blob: 165046c3e51549433ded6187aaa92ce2ef25a503 [file] [log] [blame]
// Copyright 2014 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 "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/resources/keyboard_resource_util.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_ui.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/value_builder.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/ime/dummy_text_input_client.h"
#include "ui/base/ime/init/input_method_factory.h"
#include "ui/base/ime/input_method.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/test/event_generator.h"
namespace {
const int kKeyboardHeightForTest = 100;
// TODO(shend): Remove this since all calls are synchronous now.
class KeyboardVisibleWaiter : public ChromeKeyboardControllerClient::Observer {
public:
explicit KeyboardVisibleWaiter(bool visible) : visible_(visible) {
ChromeKeyboardControllerClient::Get()->AddObserver(this);
}
~KeyboardVisibleWaiter() override {
ChromeKeyboardControllerClient::Get()->RemoveObserver(this);
}
void Wait() {
if (ChromeKeyboardControllerClient::Get()->is_keyboard_visible() ==
visible_) {
return;
}
run_loop_.Run();
}
// ChromeKeyboardControllerClient::Observer
void OnKeyboardVisibilityChanged(bool visible) override {
if (visible == visible_)
run_loop_.QuitWhenIdle();
}
private:
base::RunLoop run_loop_;
const bool visible_;
DISALLOW_COPY_AND_ASSIGN(KeyboardVisibleWaiter);
};
class KeyboardLoadedWaiter : public ChromeKeyboardControllerClient::Observer {
public:
KeyboardLoadedWaiter() {
ChromeKeyboardControllerClient::Get()->AddObserver(this);
}
~KeyboardLoadedWaiter() override {
ChromeKeyboardControllerClient::Get()->RemoveObserver(this);
}
void Wait() {
if (ChromeKeyboardControllerClient::Get()->is_keyboard_loaded())
return;
run_loop_.Run();
}
// ChromeKeyboardControllerClient::Observer
void OnKeyboardLoaded() override { run_loop_.QuitWhenIdle(); }
private:
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(KeyboardLoadedWaiter);
};
class KeyboardOccludedBoundsChangeWaiter
: public ChromeKeyboardControllerClient::Observer {
public:
KeyboardOccludedBoundsChangeWaiter() {
ChromeKeyboardControllerClient::Get()->AddObserver(this);
}
~KeyboardOccludedBoundsChangeWaiter() override {
ChromeKeyboardControllerClient::Get()->RemoveObserver(this);
}
void Wait() { run_loop_.Run(); }
// ChromeKeyboardControllerClient::Observer
void OnKeyboardOccludedBoundsChanged(const gfx::Rect& bounds) override {
run_loop_.QuitWhenIdle();
}
private:
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(KeyboardOccludedBoundsChangeWaiter);
};
ui::InputMethod* GetInputMethod() {
aura::Window* root_window = ChromeKeyboardControllerClient::Get()
->GetKeyboardWindow()
->GetRootWindow();
return root_window ? root_window->GetHost()->GetInputMethod() : nullptr;
}
} // namespace
class KeyboardControllerWebContentTest : public InProcessBrowserTest {
public:
KeyboardControllerWebContentTest() {}
~KeyboardControllerWebContentTest() override {}
void SetUp() override {
ui::SetUpInputMethodFactoryForTesting();
InProcessBrowserTest::SetUp();
}
void TearDown() override { InProcessBrowserTest::TearDown(); }
// Ensure that the virtual keyboard is enabled.
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(keyboard::switches::kEnableVirtualKeyboard);
}
protected:
void FocusEditableNodeAndShowKeyboard(const gfx::Rect& init_bounds) {
client =
std::make_unique<ui::DummyTextInputClient>(ui::TEXT_INPUT_TYPE_TEXT);
ui::InputMethod* input_method = GetInputMethod();
ASSERT_TRUE(input_method);
input_method->SetFocusedTextInputClient(client.get());
input_method->ShowVirtualKeyboardIfEnabled();
// Mock window.resizeTo that is expected to be called after navigate to a
// new virtual keyboard.
auto* keyboard_controller = ChromeKeyboardControllerClient::Get();
keyboard_controller->GetKeyboardWindow()->SetBounds(init_bounds);
}
void FocusNonEditableNode() {
client =
std::make_unique<ui::DummyTextInputClient>(ui::TEXT_INPUT_TYPE_NONE);
GetInputMethod()->SetFocusedTextInputClient(client.get());
}
void MockEnableIMEInDifferentExtension(const std::string& url,
const gfx::Rect& init_bounds) {
DCHECK(!url.empty());
auto* keyboard_controller = ChromeKeyboardControllerClient::Get();
keyboard_controller->set_virtual_keyboard_url_for_test(GURL(url));
keyboard_controller->ReloadKeyboardIfNeeded();
// Mock window.resizeTo that is expected to be called after navigate to a
// new virtual keyboard.
keyboard_controller->GetKeyboardWindow()->SetBounds(init_bounds);
}
private:
std::unique_ptr<ui::DummyTextInputClient> client;
DISALLOW_COPY_AND_ASSIGN(KeyboardControllerWebContentTest);
};
// Test for crbug.com/404340. After enabling an IME in a different extension,
// its virtual keyboard should not become visible if previous one is not.
IN_PROC_BROWSER_TEST_F(KeyboardControllerWebContentTest,
EnableIMEInDifferentExtension) {
KeyboardLoadedWaiter().Wait();
gfx::Rect test_bounds(0, 0, 0, kKeyboardHeightForTest);
FocusEditableNodeAndShowKeyboard(test_bounds);
KeyboardVisibleWaiter(true).Wait();
FocusNonEditableNode();
KeyboardVisibleWaiter(false).Wait();
MockEnableIMEInDifferentExtension("chrome-extension://domain-1", test_bounds);
// Keyboard should not become visible if previous keyboard is not.
EXPECT_FALSE(ChromeKeyboardControllerClient::Get()->is_keyboard_visible());
FocusEditableNodeAndShowKeyboard(test_bounds);
// Keyboard should become visible after focus on an editable node.
KeyboardVisibleWaiter(true).Wait();
// Simulate hide keyboard by pressing hide key on the virtual keyboard.
ChromeKeyboardControllerClient::Get()->HideKeyboard(ash::HideReason::kUser);
KeyboardVisibleWaiter(false).Wait();
MockEnableIMEInDifferentExtension("chrome-extension://domain-2", test_bounds);
// Keyboard should not become visible if previous keyboard is not, even if it
// is currently focused on an editable node.
EXPECT_FALSE(ChromeKeyboardControllerClient::Get()->is_keyboard_visible());
}
// This test requires using the Ash keyboard window for EventGenerator to work.
// TODO(stevenjb/shend): Investigate/fix.
IN_PROC_BROWSER_TEST_F(KeyboardControllerWebContentTest,
CanDragFloatingKeyboardWithMouse) {
ChromeKeyboardControllerClient::Get()->SetContainerType(
keyboard::ContainerType::kFloating, base::nullopt, base::DoNothing());
auto* controller = keyboard::KeyboardUIController::Get();
controller->ShowKeyboard(false);
KeyboardVisibleWaiter(true).Wait();
aura::Window* keyboard_window = controller->GetKeyboardWindow();
keyboard_window->SetBounds(gfx::Rect(0, 0, 100, 100));
EXPECT_EQ(gfx::Point(0, 0), keyboard_window->bounds().origin());
controller->SetDraggableArea(keyboard_window->bounds());
// Drag the top left corner of the keyboard to move it.
ui::test::EventGenerator event_generator(keyboard_window->GetRootWindow());
event_generator.MoveMouseTo(gfx::Point(0, 0));
event_generator.PressLeftButton();
event_generator.MoveMouseTo(gfx::Point(50, 50));
event_generator.ReleaseLeftButton();
event_generator.MoveMouseTo(gfx::Point(100, 100));
EXPECT_EQ(gfx::Point(50, 50), keyboard_window->bounds().origin());
}
class KeyboardControllerAppWindowTest
: public extensions::PlatformAppBrowserTest {
public:
KeyboardControllerAppWindowTest() {}
~KeyboardControllerAppWindowTest() override {}
// Ensure that the virtual keyboard is enabled.
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(keyboard::switches::kEnableVirtualKeyboard);
}
private:
DISALLOW_COPY_AND_ASSIGN(KeyboardControllerAppWindowTest);
};
// Tests that ime window won't overscroll. See crbug.com/529880.
IN_PROC_BROWSER_TEST_F(KeyboardControllerAppWindowTest,
DisableOverscrollForImeWindow) {
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "test extension")
.Set("version", "1")
.Set("manifest_version", 2)
.Set("background",
extensions::DictionaryBuilder()
.Set("scripts", extensions::ListBuilder()
.Append("background.js")
.Build())
.Build())
.Build())
.Build();
extension_service()->AddExtension(extension.get());
extensions::AppWindow::CreateParams non_ime_params;
non_ime_params.frame = extensions::AppWindow::FRAME_NONE;
extensions::AppWindow* non_ime_app_window = CreateAppWindowFromParams(
browser()->profile(), extension.get(), non_ime_params);
int non_ime_window_visible_height = non_ime_app_window->web_contents()
->GetRenderWidgetHostView()
->GetVisibleViewportSize()
.height();
extensions::AppWindow::CreateParams ime_params;
ime_params.frame = extensions::AppWindow::FRAME_NONE;
ime_params.is_ime_window = true;
extensions::AppWindow* ime_app_window = CreateAppWindowFromParams(
browser()->profile(), extension.get(), ime_params);
int ime_window_visible_height = ime_app_window->web_contents()
->GetRenderWidgetHostView()
->GetVisibleViewportSize()
.height();
ASSERT_EQ(non_ime_window_visible_height, ime_window_visible_height);
ASSERT_TRUE(ime_window_visible_height > 0);
// Make sure the keyboard has loaded before showing.
KeyboardLoadedWaiter().Wait();
auto* controller = ChromeKeyboardControllerClient::Get();
controller->ShowKeyboard();
KeyboardVisibleWaiter(true).Wait();
int screen_height = display::Screen::GetScreen()
->GetPrimaryDisplay()
.GetSizeInPixel()
.height();
int keyboard_height = screen_height - ime_window_visible_height + 1;
ASSERT_GT(keyboard_height, 0);
gfx::Rect test_bounds = controller->GetKeyboardWindow()->bounds();
test_bounds.set_height(keyboard_height);
{
// Waiter needs to be created before SetBounds() is invoked so that it can
// catch OnOccludedBoundsChanged event even before it starts waiting.
KeyboardOccludedBoundsChangeWaiter waiter;
controller->GetKeyboardWindow()->SetBounds(test_bounds);
// Wait for the keyboard bounds change has been processed.
waiter.Wait();
}
// Non ime window should have smaller visible view port due to overlap with
// virtual keyboard.
EXPECT_LT(non_ime_app_window->web_contents()
->GetRenderWidgetHostView()
->GetVisibleViewportSize()
.height(),
non_ime_window_visible_height);
// Ime window should have not be affected by virtual keyboard.
EXPECT_EQ(ime_app_window->web_contents()
->GetRenderWidgetHostView()
->GetVisibleViewportSize()
.height(),
ime_window_visible_height);
}
class KeyboardControllerStateTest : public InProcessBrowserTest {
public:
KeyboardControllerStateTest() {}
~KeyboardControllerStateTest() override {}
// Ensure that the virtual keyboard is enabled.
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(keyboard::switches::kEnableVirtualKeyboard);
}
private:
DISALLOW_COPY_AND_ASSIGN(KeyboardControllerStateTest);
};
IN_PROC_BROWSER_TEST_F(KeyboardControllerStateTest, OpenTwice) {
auto* controller = ChromeKeyboardControllerClient::Get();
EXPECT_FALSE(controller->is_keyboard_visible());
// Call ShowKeyboard twice, the keyboard should become visible.
controller->ShowKeyboard();
controller->ShowKeyboard();
KeyboardVisibleWaiter(true).Wait();
EXPECT_TRUE(controller->is_keyboard_visible());
// Ensure the keyboard remains visible. Note: we call RunUntilIdle to at least
// ensure no other messages are pending instead of relying on a timeout that
// will slow down tests and potentially be flakey.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(controller->is_keyboard_visible());
}
IN_PROC_BROWSER_TEST_F(KeyboardControllerStateTest, OpenAndCloseAndOpen) {
auto* controller = ChromeKeyboardControllerClient::Get();
controller->ShowKeyboard();
KeyboardVisibleWaiter(true).Wait();
controller->HideKeyboard(ash::HideReason::kSystem);
KeyboardVisibleWaiter(false).Wait();
controller->ShowKeyboard();
KeyboardVisibleWaiter(true).Wait();
}
// NOTE: The following tests test internal state of keyboard::KeyboardController
// and will not work in Multi Process Mash. TODO(stevenjb/shend): Determine
// whether this needs to be tested in a keyboard::KeyboardController unit test.
IN_PROC_BROWSER_TEST_F(KeyboardControllerStateTest, StateResolvesAfterPreload) {
auto* controller = keyboard::KeyboardUIController::Get();
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kLoading);
KeyboardLoadedWaiter().Wait();
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kHidden);
}
IN_PROC_BROWSER_TEST_F(KeyboardControllerStateTest,
OpenAndCloseAndOpenInternal) {
auto* controller = keyboard::KeyboardUIController::Get();
controller->ShowKeyboard(false);
// Need to wait the extension to be loaded. Hence LOADING_EXTENSION.
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kLoading);
KeyboardVisibleWaiter(true).Wait();
controller->HideKeyboardExplicitlyBySystem();
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kHidden);
controller->ShowKeyboard(false);
// The extension already has been loaded. Hence SHOWING.
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kShown);
}
// See crbug.com/755354.
IN_PROC_BROWSER_TEST_F(KeyboardControllerStateTest,
DisablingKeyboardGoesToInitialState) {
auto* controller = keyboard::KeyboardUIController::Get();
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kLoading);
controller->Shutdown();
EXPECT_EQ(controller->GetStateForTest(), keyboard::KeyboardUIState::kInitial);
}