// Copyright 2013 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 <memory>
#include "ash/accessibility/sticky_keys/sticky_keys_controller.h"
#include "ash/accessibility/sticky_keys/sticky_keys_overlay.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/unified/unified_system_tray.h"
#include "base/run_loop.h"
#include "chrome/browser/ash/accessibility/accessibility_feature_browsertest.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ash/accessibility/automation_test_utils.h"
#include "chrome/browser/ash/accessibility/select_to_speak_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/test/browser_test.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/native_widget_types.h"
namespace ash {
class StickyKeysBrowserTest : public AccessibilityFeatureBrowserTest {
protected:
StickyKeysBrowserTest() = default;
StickyKeysBrowserTest(const StickyKeysBrowserTest&) = delete;
StickyKeysBrowserTest& operator=(const StickyKeysBrowserTest&) = delete;
~StickyKeysBrowserTest() override = default;
void SetUpOnMainThread() override {
aura::Window* root_window = Shell::Get()->GetPrimaryRootWindow();
generator_ = std::make_unique<ui::test::EventGenerator>(root_window);
Profile* profile = ProfileManager::GetActiveUserProfile();
AccessibilityFeatureBrowserTest::SetUpOnMainThread();
// Load Select to Speak so we have a Javascript context with access to the
// Automation API to inject AutomationTestUtils. Select to Speak doesn't do
// any work unless it is triggered, so this does not impact the test.
sts_test_utils::TurnOnSelectToSpeakForTest(profile);
utils_ = std::make_unique<AutomationTestUtils>(
extension_misc::kSelectToSpeakExtensionId);
utils_->SetUpTestSupport();
}
void SetStickyKeysEnabled(bool enabled) {
AccessibilityManager::Get()->EnableStickyKeys(enabled);
// Spin the message loop to ensure ash sees the change.
base::RunLoop().RunUntilIdle();
}
bool IsSystemTrayBubbleOpen() {
return Shell::Get()
->GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->unified_system_tray()
->IsBubbleShown();
}
void CloseSystemTrayBubble() {
Shell::Get()
->GetPrimaryRootWindowController()
->GetStatusAreaWidget()
->unified_system_tray()
->CloseBubble();
}
void SendKeyPress(ui::KeyboardCode key) {
ui::test::EmulateFullKeyPressReleaseSequence(
generator_.get(), key,
/*control=*/false, /*shift=*/false, /*alt=*/false, /*command=*/false);
}
std::unique_ptr<ui::test::EventGenerator> generator_;
std::unique_ptr<AutomationTestUtils> utils_;
};
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, OpenTrayMenu) {
SetStickyKeysEnabled(true);
// Open system tray bubble with shortcut.
SendKeyPress(ui::VKEY_MENU); // alt key.
SendKeyPress(ui::VKEY_SHIFT);
SendKeyPress(ui::VKEY_S);
EXPECT_TRUE(IsSystemTrayBubbleOpen());
// Hide system bubble.
CloseSystemTrayBubble();
EXPECT_FALSE(IsSystemTrayBubbleOpen());
// Pressing S again should not reopen the bubble.
SendKeyPress(ui::VKEY_S);
EXPECT_FALSE(IsSystemTrayBubbleOpen());
// With sticky keys disabled, we will fail to perform the shortcut.
SetStickyKeysEnabled(false);
SendKeyPress(ui::VKEY_MENU); // alt key.
SendKeyPress(ui::VKEY_SHIFT);
SendKeyPress(ui::VKEY_S);
EXPECT_FALSE(IsSystemTrayBubbleOpen());
}
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, OpenNewTabs) {
// Lock the modifier key.
SetStickyKeysEnabled(true);
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_CONTROL);
// In the locked state, pressing 't' should open a new tab each time.
// Note Lacros starts with a "New Tab" tab, whereas Ash does not.
int tab_count = IsLacrosRunning() ? 2 : 1;
for (; tab_count < 5; ++tab_count) {
SendKeyPress(ui::VKEY_T);
utils_->WaitForNumTabsWithRegexName(tab_count, "/New Tab*/");
}
// Unlock the modifier key and shortcut should no longer activate.
// Instead, the omnibox is populated with the letter 't'.
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_T);
utils_->WaitForNodeWithClassNameAndValue("OmniboxViewViews", "t");
// Shortcut should not work after disabling sticky keys.
// Instead, another 't' is typed in the omnibox.
SetStickyKeysEnabled(false);
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_T);
utils_->WaitForNodeWithClassNameAndValue("OmniboxViewViews", "tt");
}
// Flaky. https://crbug.com/331433886.
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, DISABLED_CtrlClickHomeButton) {
// Show home page button.
browser()->profile()->GetPrefs()->SetBoolean(prefs::kShowHomeButton, true);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
int tab_count = 1;
EXPECT_EQ(tab_count, tab_strip_model->count());
// Test sticky keys with modified mouse click action.
SetStickyKeysEnabled(true);
SendKeyPress(ui::VKEY_CONTROL);
ui_test_utils::ClickOnView(browser(), VIEW_ID_HOME_BUTTON);
EXPECT_EQ(++tab_count, tab_strip_model->count());
ui_test_utils::ClickOnView(browser(), VIEW_ID_HOME_BUTTON);
EXPECT_EQ(tab_count, tab_strip_model->count());
// Test locked modifier key with mouse click.
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_CONTROL);
for (; tab_count < 5; ++tab_count) {
EXPECT_EQ(tab_count, tab_strip_model->count());
ui_test_utils::ClickOnView(browser(), VIEW_ID_HOME_BUTTON);
}
SendKeyPress(ui::VKEY_CONTROL);
ui_test_utils::ClickOnView(browser(), VIEW_ID_HOME_BUTTON);
EXPECT_EQ(tab_count, tab_strip_model->count());
// Test disabling sticky keys prevent modified mouse click.
SetStickyKeysEnabled(false);
SendKeyPress(ui::VKEY_CONTROL);
ui_test_utils::ClickOnView(browser(), VIEW_ID_HOME_BUTTON);
EXPECT_EQ(tab_count, tab_strip_model->count());
}
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, SearchLeftOmnibox) {
SetStickyKeysEnabled(true);
// Give omnibox focus by opening a new tab with ctrl+t.
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_T);
utils_->WaitForNumTabsWithRegexName(IsLacrosRunning() ? 2 : 1, "/New Tab*/");
// Type 'foo'.
SendKeyPress(ui::VKEY_F);
SendKeyPress(ui::VKEY_O);
SendKeyPress(ui::VKEY_O);
utils_->WaitForNodeWithClassNameAndValue("OmniboxViewViews", "foo");
// Hit Home by sequencing Search (left Windows) and Left (arrow).
SendKeyPress(ui::VKEY_LWIN);
SendKeyPress(ui::VKEY_LEFT);
// Verify caret moved to the beginning by typing something else, this
// should appear before "foo" in the omnibox.
SendKeyPress(ui::VKEY_B);
SendKeyPress(ui::VKEY_A);
SendKeyPress(ui::VKEY_R);
utils_->WaitForNodeWithClassNameAndValue("OmniboxViewViews", "barfoo");
}
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, OverlayShown) {
const ui::KeyboardCode modifier_keys[] = {ui::VKEY_CONTROL, ui::VKEY_SHIFT,
ui::VKEY_MENU, ui::VKEY_COMMAND};
// Overlay should not be visible if sticky keys is not enabled.
StickyKeysController* controller = Shell::Get()->sticky_keys_controller();
EXPECT_FALSE(controller->GetOverlayForTest());
for (auto key_code : modifier_keys) {
SendKeyPress(key_code);
EXPECT_FALSE(controller->GetOverlayForTest());
}
// Cycle through the modifier keys and make sure each gets shown.
SetStickyKeysEnabled(true);
StickyKeysOverlay* sticky_keys_overlay = controller->GetOverlayForTest();
for (auto key_code : modifier_keys) {
SendKeyPress(key_code);
EXPECT_TRUE(sticky_keys_overlay->is_visible());
SendKeyPress(key_code);
EXPECT_TRUE(sticky_keys_overlay->is_visible());
SendKeyPress(key_code);
EXPECT_FALSE(sticky_keys_overlay->is_visible());
}
// Disabling sticky keys should hide the overlay.
SendKeyPress(ui::VKEY_CONTROL);
EXPECT_TRUE(sticky_keys_overlay->is_visible());
SetStickyKeysEnabled(false);
EXPECT_FALSE(controller->GetOverlayForTest());
for (auto key_code : modifier_keys) {
SendKeyPress(key_code);
EXPECT_FALSE(controller->GetOverlayForTest());
}
}
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, OpenIncognitoWindow) {
SetStickyKeysEnabled(true);
StickyKeysOverlay* overlay =
Shell::Get()->sticky_keys_controller()->GetOverlayForTest();
ASSERT_TRUE(overlay);
// Overlay is shown on first modifier key press.
EXPECT_FALSE(overlay->is_visible());
SendKeyPress(ui::VKEY_SHIFT);
EXPECT_TRUE(overlay->is_visible());
SendKeyPress(ui::VKEY_CONTROL);
EXPECT_TRUE(overlay->is_visible());
SendKeyPress(ui::VKEY_N);
EXPECT_FALSE(overlay->is_visible());
utils_->WaitForNumTabsWithRegexName(1, "/New Incognito Tab*/");
}
IN_PROC_BROWSER_TEST_F(StickyKeysBrowserTest, CyclesWindows) {
SetStickyKeysEnabled(true);
// Ensure there is a normal browser window open with ctrl+t.
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_T);
int expected_tabs = IsLacrosRunning() ? 2 : 1;
utils_->WaitForNumTabsWithRegexName(expected_tabs, "/New Tab*/");
// Open an incognito browser.
SendKeyPress(ui::VKEY_SHIFT);
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_N);
utils_->WaitForNumTabsWithRegexName(1, "/New Incognito Tab*/");
// Ctrl+t opens another incognito tab because the incognito window is focused.
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_T);
utils_->WaitForNumTabsWithRegexName(2, "/New Incognito Tab*/");
// Cycle between windows.
SendKeyPress(ui::VKEY_MENU); // alt key.
SendKeyPress(ui::VKEY_TAB);
// Ctrl+t opens another non-incognito tab now, because the normal browser
// window is focused.
SendKeyPress(ui::VKEY_CONTROL);
SendKeyPress(ui::VKEY_T);
expected_tabs++;
utils_->WaitForNumTabsWithRegexName(expected_tabs, "/New Tab*/");
}
} // namespace ash