Commit 357b18ca authored by bshe@chromium.org's avatar bshe@chromium.org

Delay virtual keyboard hiding for 100ms


This CL should fix this issue that we are experiencing(note it is not always reproducible):
- In a maximized window, clicking button may not take any effect due to immediate
keyboard relayout
(go to http://unixpapa.com/js/testkey.html and try hit reset button after typed
a bunch of keys)

It also workarounds the other two issues:
1. keyboard flicking when quickly switch from one input field to another field
2. In webdev javascript console, the input feild temoporary lost focus when
candidate popup window shows and it may try to hide and relayout keyboard, which
makes the console unuseable with virtual keyboard.
The root problem for the above two issues are tracked in issue 281493.

BUG=

Review URL: https://chromiumcodereview.appspot.com/22831045

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220648 0039d316-1c4b-4281-b951-d872f2087c98
parent ea5ace2e
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "ui/keyboard/keyboard_controller.h" #include "ui/keyboard/keyboard_controller.h"
#include "base/bind.h"
#include "ui/aura/layout_manager.h" #include "ui/aura/layout_manager.h"
#include "ui/aura/window.h" #include "ui/aura/window.h"
#include "ui/aura/window_delegate.h" #include "ui/aura/window_delegate.h"
...@@ -19,6 +20,8 @@ ...@@ -19,6 +20,8 @@
namespace { namespace {
const int kHideKeyboardDelayMs = 100;
gfx::Rect KeyboardBoundsFromWindowBounds(const gfx::Rect& window_bounds) { gfx::Rect KeyboardBoundsFromWindowBounds(const gfx::Rect& window_bounds) {
const float kKeyboardHeightRatio = 0.3f; const float kKeyboardHeightRatio = 0.3f;
return gfx::Rect( return gfx::Rect(
...@@ -118,7 +121,9 @@ class KeyboardLayoutManager : public aura::LayoutManager { ...@@ -118,7 +121,9 @@ class KeyboardLayoutManager : public aura::LayoutManager {
KeyboardController::KeyboardController(KeyboardControllerProxy* proxy) KeyboardController::KeyboardController(KeyboardControllerProxy* proxy)
: proxy_(proxy), : proxy_(proxy),
container_(NULL), container_(NULL),
input_method_(NULL) { input_method_(NULL),
keyboard_visible_(false),
weak_factory_(this) {
CHECK(proxy); CHECK(proxy);
input_method_ = proxy_->GetInputMethod(); input_method_ = proxy_->GetInputMethod();
input_method_->AddObserver(this); input_method_->AddObserver(this);
...@@ -166,7 +171,7 @@ void KeyboardController::OnTextInputStateChanged( ...@@ -166,7 +171,7 @@ void KeyboardController::OnTextInputStateChanged(
if (!container_) if (!container_)
return; return;
bool was_showing = container_->IsVisible(); bool was_showing = keyboard_visible_;
bool should_show = was_showing; bool should_show = was_showing;
if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) { if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) {
should_show = false; should_show = false;
...@@ -182,20 +187,26 @@ void KeyboardController::OnTextInputStateChanged( ...@@ -182,20 +187,26 @@ void KeyboardController::OnTextInputStateChanged(
} }
if (was_showing != should_show) { if (was_showing != should_show) {
gfx::Rect new_bounds( if (should_show) {
should_show ? container_->children()[0]->bounds() : gfx::Rect()); keyboard_visible_ = true;
weak_factory_.InvalidateWeakPtrs();
FOR_EACH_OBSERVER( if (container_->IsVisible())
KeyboardControllerObserver, return;
observer_list_,
OnKeyboardBoundsChanging(new_bounds)); FOR_EACH_OBSERVER(
KeyboardControllerObserver,
if (should_show) observer_list_,
OnKeyboardBoundsChanging(container_->children()[0]->bounds()));
proxy_->ShowKeyboardContainer(container_); proxy_->ShowKeyboardContainer(container_);
else } else {
proxy_->HideKeyboardContainer(container_); keyboard_visible_ = false;
base::MessageLoop::current()->PostDelayedTask(
FROM_HERE,
base::Bind(&KeyboardController::HideKeyboard,
weak_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kHideKeyboardDelayMs));
}
} }
// TODO(bryeung): whenever the TextInputClient changes we need to notify the // TODO(bryeung): whenever the TextInputClient changes we need to notify the
// keyboard (with the TextInputType) so that it can reset it's state (e.g. // keyboard (with the TextInputType) so that it can reset it's state (e.g.
// abandon compositions in progress) // abandon compositions in progress)
...@@ -207,4 +218,15 @@ void KeyboardController::OnInputMethodDestroyed( ...@@ -207,4 +218,15 @@ void KeyboardController::OnInputMethodDestroyed(
input_method_ = NULL; input_method_ = NULL;
} }
void KeyboardController::HideKeyboard() {
FOR_EACH_OBSERVER(KeyboardControllerObserver,
observer_list_,
OnKeyboardBoundsChanging(gfx::Rect()));
proxy_->HideKeyboardContainer(container_);
}
bool KeyboardController::WillHideKeyboard() const {
return weak_factory_.HasWeakPtrs();
}
} // namespace keyboard } // namespace keyboard
...@@ -61,9 +61,19 @@ class KEYBOARD_EXPORT KeyboardController : public ui::InputMethodObserver, ...@@ -61,9 +61,19 @@ class KEYBOARD_EXPORT KeyboardController : public ui::InputMethodObserver,
virtual void OnInputMethodDestroyed( virtual void OnInputMethodDestroyed(
const ui::InputMethod* input_method) OVERRIDE; const ui::InputMethod* input_method) OVERRIDE;
// Hides virtual keyboard and notifies observer bounds change.
// This functions should be called with a delay to avoid layout flicker
// when the focus of input field quickly change.
void HideKeyboard();
// Returns true if keyboard is scheduled to hide.
bool WillHideKeyboard() const;
scoped_ptr<KeyboardControllerProxy> proxy_; scoped_ptr<KeyboardControllerProxy> proxy_;
aura::Window* container_; aura::Window* container_;
ui::InputMethod* input_method_; ui::InputMethod* input_method_;
bool keyboard_visible_;
base::WeakPtrFactory<KeyboardController> weak_factory_;
ObserverList<KeyboardControllerObserver> observer_list_; ObserverList<KeyboardControllerObserver> observer_list_;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/bind.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -158,6 +159,27 @@ class TestTextInputClient : public ui::TextInputClient { ...@@ -158,6 +159,27 @@ class TestTextInputClient : public ui::TextInputClient {
DISALLOW_COPY_AND_ASSIGN(TestTextInputClient); DISALLOW_COPY_AND_ASSIGN(TestTextInputClient);
}; };
class KeyboardContainerObserver : public aura::WindowObserver {
public:
explicit KeyboardContainerObserver(aura::Window* window) : window_(window) {
window_->AddObserver(this);
}
virtual ~KeyboardContainerObserver() {
window_->RemoveObserver(this);
}
private:
virtual void OnWindowVisibilityChanged(aura::Window* window,
bool visible) OVERRIDE {
if (!visible)
base::MessageLoop::current()->Quit();
}
aura::Window* window_;
DISALLOW_COPY_AND_ASSIGN(KeyboardContainerObserver);
};
} // namespace } // namespace
class KeyboardControllerTest : public testing::Test { class KeyboardControllerTest : public testing::Test {
...@@ -170,6 +192,8 @@ class KeyboardControllerTest : public testing::Test { ...@@ -170,6 +192,8 @@ class KeyboardControllerTest : public testing::Test {
aura_test_helper_->SetUp(); aura_test_helper_->SetUp();
ui::SetUpInputMethodFactoryForTesting(); ui::SetUpInputMethodFactoryForTesting();
focus_controller_.reset(new TestFocusController(root_window())); focus_controller_.reset(new TestFocusController(root_window()));
proxy_ = new TestKeyboardControllerProxy();
controller_.reset(new KeyboardController(proxy_));
} }
virtual void TearDown() OVERRIDE { virtual void TearDown() OVERRIDE {
...@@ -178,37 +202,43 @@ class KeyboardControllerTest : public testing::Test { ...@@ -178,37 +202,43 @@ class KeyboardControllerTest : public testing::Test {
} }
aura::RootWindow* root_window() { return aura_test_helper_->root_window(); } aura::RootWindow* root_window() { return aura_test_helper_->root_window(); }
KeyboardControllerProxy* proxy() { return proxy_; }
KeyboardController* controller() { return controller_.get(); }
void ShowKeyboard(KeyboardController* controller) { void ShowKeyboard() {
TestTextInputClient test_text_input_client(ui::TEXT_INPUT_TYPE_TEXT); TestTextInputClient test_text_input_client(ui::TEXT_INPUT_TYPE_TEXT);
controller->OnTextInputStateChanged(&test_text_input_client); controller_->OnTextInputStateChanged(&test_text_input_client);
} }
protected: protected:
bool WillHideKeyboard() {
return controller_->WillHideKeyboard();
}
base::MessageLoopForUI message_loop_; base::MessageLoopForUI message_loop_;
scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_; scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
scoped_ptr<TestFocusController> focus_controller_; scoped_ptr<TestFocusController> focus_controller_;
private: private:
KeyboardControllerProxy* proxy_;
scoped_ptr<KeyboardController> controller_;
DISALLOW_COPY_AND_ASSIGN(KeyboardControllerTest); DISALLOW_COPY_AND_ASSIGN(KeyboardControllerTest);
}; };
TEST_F(KeyboardControllerTest, KeyboardSize) { TEST_F(KeyboardControllerTest, KeyboardSize) {
KeyboardControllerProxy* proxy = new TestKeyboardControllerProxy(); scoped_ptr<aura::Window> container(controller()->GetContainerWindow());
KeyboardController controller(proxy);
scoped_ptr<aura::Window> container(controller.GetContainerWindow());
gfx::Rect bounds(0, 0, 100, 100); gfx::Rect bounds(0, 0, 100, 100);
container->SetBounds(bounds); container->SetBounds(bounds);
const gfx::Rect& before_bounds = proxy->GetKeyboardWindow()->bounds(); const gfx::Rect& before_bounds = proxy()->GetKeyboardWindow()->bounds();
gfx::Rect new_bounds( gfx::Rect new_bounds(
before_bounds.x(), before_bounds.y(), before_bounds.x(), before_bounds.y(),
before_bounds.width() / 2, before_bounds.height() / 2); before_bounds.width() / 2, before_bounds.height() / 2);
// The KeyboardController's LayoutManager shouldn't let this happen // The KeyboardController's LayoutManager shouldn't let this happen
proxy->GetKeyboardWindow()->SetBounds(new_bounds); proxy()->GetKeyboardWindow()->SetBounds(new_bounds);
ASSERT_EQ(before_bounds, proxy->GetKeyboardWindow()->bounds()); ASSERT_EQ(before_bounds, proxy()->GetKeyboardWindow()->bounds());
} }
// Tests that tapping/clicking inside the keyboard does not give it focus. // Tests that tapping/clicking inside the keyboard does not give it focus.
...@@ -222,16 +252,14 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) { ...@@ -222,16 +252,14 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) {
window->Show(); window->Show();
window->Focus(); window->Focus();
KeyboardControllerProxy* proxy = new TestKeyboardControllerProxy(); scoped_ptr<aura::Window> keyboard_container(
KeyboardController controller(proxy); controller()->GetContainerWindow());
scoped_ptr<aura::Window> keyboard_container(controller.GetContainerWindow());
keyboard_container->SetBounds(root_bounds); keyboard_container->SetBounds(root_bounds);
root_window()->AddChild(keyboard_container.get()); root_window()->AddChild(keyboard_container.get());
keyboard_container->Show(); keyboard_container->Show();
ShowKeyboard(&controller); ShowKeyboard();
EXPECT_TRUE(window->IsVisible()); EXPECT_TRUE(window->IsVisible());
EXPECT_TRUE(keyboard_container->IsVisible()); EXPECT_TRUE(keyboard_container->IsVisible());
...@@ -244,7 +272,7 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) { ...@@ -244,7 +272,7 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) {
keyboard_container->AddPreTargetHandler(&observer); keyboard_container->AddPreTargetHandler(&observer);
aura::test::EventGenerator generator(root_window()); aura::test::EventGenerator generator(root_window());
generator.MoveMouseTo(proxy->GetKeyboardWindow()->bounds().CenterPoint()); generator.MoveMouseTo(proxy()->GetKeyboardWindow()->bounds().CenterPoint());
generator.ClickLeftButton(); generator.ClickLeftButton();
EXPECT_TRUE(window->HasFocus()); EXPECT_TRUE(window->HasFocus());
EXPECT_FALSE(keyboard_container->HasFocus()); EXPECT_FALSE(keyboard_container->HasFocus());
...@@ -260,32 +288,43 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) { ...@@ -260,32 +288,43 @@ TEST_F(KeyboardControllerTest, ClickDoesNotFocusKeyboard) {
TEST_F(KeyboardControllerTest, VisibilityChangeWithTextInputTypeChange) { TEST_F(KeyboardControllerTest, VisibilityChangeWithTextInputTypeChange) {
const gfx::Rect& root_bounds = root_window()->bounds(); const gfx::Rect& root_bounds = root_window()->bounds();
aura::test::EventCountDelegate delegate;
scoped_ptr<aura::Window> window(new aura::Window(&delegate));
window->Init(ui::LAYER_NOT_DRAWN);
window->SetBounds(root_bounds);
root_window()->AddChild(window.get());
window->Show();
window->Focus();
KeyboardControllerProxy* proxy = new TestKeyboardControllerProxy();
ui::InputMethod* input_method = proxy->GetInputMethod();
TestTextInputClient input_client(ui::TEXT_INPUT_TYPE_TEXT);
TestTextInputClient no_input_client(ui::TEXT_INPUT_TYPE_NONE);
input_method->SetFocusedTextInputClient(&input_client);
KeyboardController controller(proxy); ui::InputMethod* input_method = proxy()->GetInputMethod();
TestTextInputClient input_client_0(ui::TEXT_INPUT_TYPE_TEXT);
scoped_ptr<aura::Window> keyboard_container(controller.GetContainerWindow()); TestTextInputClient input_client_1(ui::TEXT_INPUT_TYPE_TEXT);
TestTextInputClient input_client_2(ui::TEXT_INPUT_TYPE_TEXT);
TestTextInputClient no_input_client_0(ui::TEXT_INPUT_TYPE_NONE);
TestTextInputClient no_input_client_1(ui::TEXT_INPUT_TYPE_NONE);
input_method->SetFocusedTextInputClient(&input_client_0);
scoped_ptr<aura::Window> keyboard_container(
controller()->GetContainerWindow());
scoped_ptr<KeyboardContainerObserver> keyboard_container_observer(
new KeyboardContainerObserver(keyboard_container.get()));
keyboard_container->SetBounds(root_bounds); keyboard_container->SetBounds(root_bounds);
root_window()->AddChild(keyboard_container.get()); root_window()->AddChild(keyboard_container.get());
EXPECT_TRUE(keyboard_container->IsVisible()); EXPECT_TRUE(keyboard_container->IsVisible());
input_method->SetFocusedTextInputClient(&no_input_client); input_method->SetFocusedTextInputClient(&no_input_client_0);
// Keyboard should not immediately hide itself. It is delayed to avoid layout
// flicker when the focus of input field quickly change.
EXPECT_TRUE(keyboard_container->IsVisible());
EXPECT_TRUE(WillHideKeyboard());
// Wait for hide keyboard to finish.
base::MessageLoop::current()->Run();
EXPECT_FALSE(keyboard_container->IsVisible()); EXPECT_FALSE(keyboard_container->IsVisible());
input_method->SetFocusedTextInputClient(&input_client); input_method->SetFocusedTextInputClient(&input_client_1);
EXPECT_TRUE(keyboard_container->IsVisible());
// Schedule to hide keyboard.
input_method->SetFocusedTextInputClient(&no_input_client_1);
EXPECT_TRUE(WillHideKeyboard());
// Cancel keyboard hide.
input_method->SetFocusedTextInputClient(&input_client_2);
EXPECT_FALSE(WillHideKeyboard());
EXPECT_TRUE(keyboard_container->IsVisible()); EXPECT_TRUE(keyboard_container->IsVisible());
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment