summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYukawa@chromium.org <Yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-13 16:13:35 +0000
committerYukawa@chromium.org <Yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-13 16:13:35 +0000
commit38ef5018776ab633f1b2fcafaf456b453a6be8f6 (patch)
treead1c108ecae06b755bfe41d4c6699a3976ba40ad
parent81b4bbd6e5edaa9449c2d48e2ffc5e58318694e1 (diff)
downloadchromium_src-38ef5018776ab633f1b2fcafaf456b453a6be8f6.zip
chromium_src-38ef5018776ab633f1b2fcafaf456b453a6be8f6.tar.gz
chromium_src-38ef5018776ab633f1b2fcafaf456b453a6be8f6.tar.bz2
Add sticky focus mechanism into ui::InputMethod
Currently there are two callers of ui::InputMethod::SetFocusedTextInputClient. One is RenderWidgetHostViewAura and the other is ui::views::InputMethodBridge. This makes it hard to implement ui::InputMethod correctly because these callers work independently to each other. As a tentative solution, this CL introduces "sticky" focus mechanism to solve this problem. It will be used as follows in the next CL. 1. RWHVA calls SetStickyFocusedTextInputClient(this) -> "FocusedTextInputClient" is locked to RWHVA 2. InputMethodBridge calls SetFocusedTextInputClient(this) -> This will be ignored because RWHVA already locked the focus. 3. RWHVA calls SetStickyFocusedTextInputClient(NULL) -> "FocusedTextInputClient" is unlocked 4. InputMethodBridge calls SetFocusedTextInputClient(this) -> "FocusedTextInputClient" is set to InputMethodBridge In short, InputMethodBridge can gain text input focus as long as RWHVA does not own the input focus. This special rule solves the problem. In a long term solution, @nona is working on an unified text input client focus manager, which is expected to replace this sticky focus mechanism. I hope it will happen near future. This CL also fixes a minor issue that InputMethodBase::On{Will/Did}ChangeFocusedClient are called redundantly when InputMethod::SetFocusedTextInputClient is called twice or more. Except for this minor fix, there CL never changes the production code. BUG=287620 TEST=Ran unit tests. Manually done with/without --enable-text-services-framework on Aura/non-Aura Windows 7. Review URL: https://chromiumcodereview.appspot.com/23754015 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@223054 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--ui/base/ime/dummy_input_method.cc4
-rw-r--r--ui/base/ime/dummy_input_method.h2
-rw-r--r--ui/base/ime/dummy_text_input_client.cc101
-rw-r--r--ui/base/ime/dummy_text_input_client.h49
-rw-r--r--ui/base/ime/fake_input_method.cc13
-rw-r--r--ui/base/ime/fake_input_method.h3
-rw-r--r--ui/base/ime/ime_test_support.gypi2
-rw-r--r--ui/base/ime/ime_unittests.gypi18
-rw-r--r--ui/base/ime/input_method.h8
-rw-r--r--ui/base/ime/input_method_base.cc26
-rw-r--r--ui/base/ime/input_method_base.h5
-rw-r--r--ui/base/ime/input_method_base_unittest.cc383
-rw-r--r--ui/base/ime/mock_input_method.cc18
-rw-r--r--ui/base/ime/mock_input_method.h3
14 files changed, 618 insertions, 17 deletions
diff --git a/ui/base/ime/dummy_input_method.cc b/ui/base/ime/dummy_input_method.cc
index b35313d..2fc206d 100644
--- a/ui/base/ime/dummy_input_method.cc
+++ b/ui/base/ime/dummy_input_method.cc
@@ -32,6 +32,10 @@ bool DummyInputMethod::OnUntranslatedIMEMessage(const base::NativeEvent& event,
void DummyInputMethod::SetFocusedTextInputClient(TextInputClient* client) {
}
+void DummyInputMethod::SetStickyFocusedTextInputClient(
+ TextInputClient* client) {
+}
+
void DummyInputMethod::DetachTextInputClient(TextInputClient* client) {
}
diff --git a/ui/base/ime/dummy_input_method.h b/ui/base/ime/dummy_input_method.h
index 0e40c18..edceb0e 100644
--- a/ui/base/ime/dummy_input_method.h
+++ b/ui/base/ime/dummy_input_method.h
@@ -25,6 +25,8 @@ class DummyInputMethod : public InputMethod {
virtual bool OnUntranslatedIMEMessage(
const base::NativeEvent& event, NativeEventResult* result) OVERRIDE;
virtual void SetFocusedTextInputClient(TextInputClient* client) OVERRIDE;
+ virtual void SetStickyFocusedTextInputClient(
+ TextInputClient* client) OVERRIDE;
virtual void DetachTextInputClient(TextInputClient* client) OVERRIDE;
virtual TextInputClient* GetTextInputClient() const OVERRIDE;
virtual bool DispatchKeyEvent(const base::NativeEvent& event) OVERRIDE;
diff --git a/ui/base/ime/dummy_text_input_client.cc b/ui/base/ime/dummy_text_input_client.cc
new file mode 100644
index 0000000..c12daef
--- /dev/null
+++ b/ui/base/ime/dummy_text_input_client.cc
@@ -0,0 +1,101 @@
+// Copyright 2013 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 "ui/base/ime/dummy_text_input_client.h"
+#include "ui/gfx/rect.h"
+
+namespace ui {
+
+DummyTextInputClient::DummyTextInputClient() {
+}
+
+DummyTextInputClient::~DummyTextInputClient() {
+}
+
+void DummyTextInputClient::SetCompositionText(
+ const ui::CompositionText& composition) {
+}
+
+void DummyTextInputClient::ConfirmCompositionText() {
+}
+
+void DummyTextInputClient::ClearCompositionText() {
+}
+
+void DummyTextInputClient::InsertText(const string16& text) {
+}
+
+void DummyTextInputClient::InsertChar(char16 ch, int flags) {
+}
+
+gfx::NativeWindow DummyTextInputClient::GetAttachedWindow() const {
+ return NULL;
+}
+
+ui::TextInputType DummyTextInputClient::GetTextInputType() const {
+ return TEXT_INPUT_TYPE_NONE;
+}
+
+ui::TextInputMode DummyTextInputClient::GetTextInputMode() const {
+ return TEXT_INPUT_MODE_DEFAULT;
+}
+
+bool DummyTextInputClient::CanComposeInline() const {
+ return false;
+}
+
+gfx::Rect DummyTextInputClient::GetCaretBounds() {
+ return gfx::Rect();
+}
+
+bool DummyTextInputClient::GetCompositionCharacterBounds(uint32 index,
+ gfx::Rect* rect) {
+ return false;
+}
+
+bool DummyTextInputClient::HasCompositionText() {
+ return false;
+}
+
+bool DummyTextInputClient::GetTextRange(gfx::Range* range) {
+ return false;
+}
+
+bool DummyTextInputClient::GetCompositionTextRange(gfx::Range* range) {
+ return false;
+}
+
+bool DummyTextInputClient::GetSelectionRange(gfx::Range* range) {
+ return false;
+}
+
+bool DummyTextInputClient::SetSelectionRange(const gfx::Range& range) {
+ return false;
+}
+
+bool DummyTextInputClient::DeleteRange(const gfx::Range& range) {
+ return false;
+}
+
+bool DummyTextInputClient::GetTextFromRange(const gfx::Range& range,
+ string16* text) {
+ return false;
+}
+
+void DummyTextInputClient::OnInputMethodChanged() {
+}
+
+bool DummyTextInputClient::ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) {
+ return false;
+}
+
+void DummyTextInputClient::ExtendSelectionAndDelete(size_t before,
+ size_t after) {
+}
+
+void DummyTextInputClient::EnsureCaretInRect(const gfx::Rect& rect) {
+}
+
+} // namespace ui
diff --git a/ui/base/ime/dummy_text_input_client.h b/ui/base/ime/dummy_text_input_client.h
new file mode 100644
index 0000000..966502f
--- /dev/null
+++ b/ui/base/ime/dummy_text_input_client.h
@@ -0,0 +1,49 @@
+// Copyright 2013 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.
+
+#ifndef UI_BASE_IME_DUMMY_TEXT_INPUT_CLIENT_H_
+#define UI_BASE_IME_DUMMY_TEXT_INPUT_CLIENT_H_
+
+#include "ui/base/ime/text_input_client.h"
+
+namespace ui {
+
+// Dummy implementation of TextInputClient. All functions do nothing.
+class DummyTextInputClient : public TextInputClient {
+ public:
+ DummyTextInputClient();
+ virtual ~DummyTextInputClient();
+
+ // Overriden from TextInputClient.
+ virtual void SetCompositionText(
+ const ui::CompositionText& composition) OVERRIDE;
+ virtual void ConfirmCompositionText() OVERRIDE;
+ virtual void ClearCompositionText() OVERRIDE;
+ virtual void InsertText(const string16& text) OVERRIDE;
+ virtual void InsertChar(char16 ch, int flags) OVERRIDE;
+ virtual gfx::NativeWindow GetAttachedWindow() const OVERRIDE;
+ virtual ui::TextInputType GetTextInputType() const OVERRIDE;
+ virtual ui::TextInputMode GetTextInputMode() const OVERRIDE;
+ virtual bool CanComposeInline() const OVERRIDE;
+ virtual gfx::Rect GetCaretBounds() OVERRIDE;
+ virtual bool GetCompositionCharacterBounds(uint32 index,
+ gfx::Rect* rect) OVERRIDE;
+ virtual bool HasCompositionText() OVERRIDE;
+ virtual bool GetTextRange(gfx::Range* range) OVERRIDE;
+ virtual bool GetCompositionTextRange(gfx::Range* range) OVERRIDE;
+ virtual bool GetSelectionRange(gfx::Range* range) OVERRIDE;
+ virtual bool SetSelectionRange(const gfx::Range& range) OVERRIDE;
+ virtual bool DeleteRange(const gfx::Range& range) OVERRIDE;
+ virtual bool GetTextFromRange(const gfx::Range& range,
+ string16* text) OVERRIDE;
+ virtual void OnInputMethodChanged() OVERRIDE;
+ virtual bool ChangeTextDirectionAndLayoutAlignment(
+ base::i18n::TextDirection direction) OVERRIDE;
+ virtual void ExtendSelectionAndDelete(size_t before, size_t after) OVERRIDE;
+ virtual void EnsureCaretInRect(const gfx::Rect& rect) OVERRIDE;
+};
+
+} // namespace ui
+
+#endif // UI_BASE_IME_DUMMY_TEXT_INPUT_CLIENT_H_
diff --git a/ui/base/ime/fake_input_method.cc b/ui/base/ime/fake_input_method.cc
index 952108c..fd2cb719 100644
--- a/ui/base/ime/fake_input_method.cc
+++ b/ui/base/ime/fake_input_method.cc
@@ -38,7 +38,8 @@ namespace ui {
FakeInputMethod::FakeInputMethod(internal::InputMethodDelegate* delegate)
: delegate_(NULL),
- text_input_client_(NULL) {
+ text_input_client_(NULL),
+ is_sticky_text_input_client_(false) {
SetDelegate(delegate);
}
@@ -50,14 +51,24 @@ void FakeInputMethod::SetDelegate(internal::InputMethodDelegate* delegate) {
}
void FakeInputMethod::SetFocusedTextInputClient(TextInputClient* client) {
+ if (is_sticky_text_input_client_)
+ return;
text_input_client_ = client;
FOR_EACH_OBSERVER(InputMethodObserver, observers_,
OnTextInputStateChanged(client));
}
+void FakeInputMethod::SetStickyFocusedTextInputClient(TextInputClient* client) {
+ text_input_client_ = client;
+ is_sticky_text_input_client_ = (client != NULL);
+ FOR_EACH_OBSERVER(InputMethodObserver, observers_,
+ OnTextInputStateChanged(client));
+}
+
void FakeInputMethod::DetachTextInputClient(TextInputClient* client) {
if (text_input_client_ == client) {
text_input_client_ = NULL;
+ is_sticky_text_input_client_ = false;
FOR_EACH_OBSERVER(InputMethodObserver, observers_,
OnTextInputStateChanged(client));
}
diff --git a/ui/base/ime/fake_input_method.h b/ui/base/ime/fake_input_method.h
index 9aca97c..42b8b02 100644
--- a/ui/base/ime/fake_input_method.h
+++ b/ui/base/ime/fake_input_method.h
@@ -34,6 +34,8 @@ class UI_EXPORT FakeInputMethod : NON_EXPORTED_BASE(public InputMethod) {
virtual bool OnUntranslatedIMEMessage(const base::NativeEvent& event,
NativeEventResult* result) OVERRIDE;
virtual void SetFocusedTextInputClient(TextInputClient* client) OVERRIDE;
+ virtual void SetStickyFocusedTextInputClient(
+ TextInputClient* client) OVERRIDE;
virtual void DetachTextInputClient(TextInputClient* client) OVERRIDE;
virtual TextInputClient* GetTextInputClient() const OVERRIDE;
virtual bool DispatchKeyEvent(const base::NativeEvent& native_event) OVERRIDE;
@@ -55,6 +57,7 @@ class UI_EXPORT FakeInputMethod : NON_EXPORTED_BASE(public InputMethod) {
private:
internal::InputMethodDelegate* delegate_;
TextInputClient* text_input_client_;
+ bool is_sticky_text_input_client_;
ObserverList<InputMethodObserver> observers_;
DISALLOW_COPY_AND_ASSIGN(FakeInputMethod);
diff --git a/ui/base/ime/ime_test_support.gypi b/ui/base/ime/ime_test_support.gypi
index a6ac361..02fa6eb 100644
--- a/ui/base/ime/ime_test_support.gypi
+++ b/ui/base/ime/ime_test_support.gypi
@@ -6,6 +6,8 @@
'sources': [
'dummy_input_method.cc',
'dummy_input_method.h',
+ 'dummy_text_input_client.cc',
+ 'dummy_text_input_client.h',
'win/mock_tsf_bridge.cc',
'win/mock_tsf_bridge.h',
],
diff --git a/ui/base/ime/ime_unittests.gypi b/ui/base/ime/ime_unittests.gypi
index 10a087fc..3dabb50 100644
--- a/ui/base/ime/ime_unittests.gypi
+++ b/ui/base/ime/ime_unittests.gypi
@@ -3,14 +3,10 @@
# found in the LICENSE file.
{
- 'variables': {
- 'ime_test_files': [
- 'character_composer_unittest.cc',
- 'input_method_ibus_unittest.cc',
- ],
- },
'sources': [
- '<@(ime_test_files)',
+ 'character_composer_unittest.cc',
+ 'input_method_base_unittest.cc',
+ 'input_method_ibus_unittest.cc',
'win/imm32_manager_unittest.cc',
'win/tsf_input_scope_unittest.cc',
'win/tsf_text_store_unittest.cc',
@@ -18,7 +14,13 @@
'conditions': [
['use_aura==0 or use_x11==0 or chromeos==0', {
'sources!': [
- '<@(ime_test_files)',
+ 'character_composer_unittest.cc',
+ 'input_method_ibus_unittest.cc',
+ ],
+ }],
+ ['use_aura==0 and OS!="win"', {
+ 'sources!': [
+ 'input_method_base_unittest.cc',
],
}],
['OS!="win"', {
diff --git a/ui/base/ime/input_method.h b/ui/base/ime/input_method.h
index 4deaa54..735b0e9 100644
--- a/ui/base/ime/input_method.h
+++ b/ui/base/ime/input_method.h
@@ -86,6 +86,14 @@ class InputMethod {
// calling the method with NULL when it is unfocused.
virtual void SetFocusedTextInputClient(TextInputClient* client) = 0;
+ // A variant of SetFocusedTextInputClient. Unlike SetFocusedTextInputClient,
+ // all the subsequent calls of SetFocusedTextInputClient will be ignored
+ // until |client| is detached. This method is introduced as a workaround
+ // against crbug.com/287620.
+ // NOTE: You can pass NULL to |client| to detach the sticky client.
+ // NOTE: You can also use DetachTextInputClient to remove the sticky client.
+ virtual void SetStickyFocusedTextInputClient(TextInputClient* client) = 0;
+
// Detaches and forgets the |client| regardless of whether it has the focus or
// not. This method is meant to be called when the |client| is going to be
// destroyed.
diff --git a/ui/base/ime/input_method_base.cc b/ui/base/ime/input_method_base.cc
index 1ab7942..d377076 100644
--- a/ui/base/ime/input_method_base.cc
+++ b/ui/base/ime/input_method_base.cc
@@ -14,6 +14,7 @@ namespace ui {
InputMethodBase::InputMethodBase()
: delegate_(NULL),
text_input_client_(NULL),
+ is_sticky_text_input_client_(false),
system_toplevel_window_focused_(false) {
}
@@ -43,19 +44,21 @@ void InputMethodBase::OnBlur() {
}
void InputMethodBase::SetFocusedTextInputClient(TextInputClient* client) {
- TextInputClient* old = text_input_client_;
- OnWillChangeFocusedClient(old, client);
- text_input_client_ = client; // NULL allowed.
- OnDidChangeFocusedClient(old, client);
+ if (is_sticky_text_input_client_)
+ return;
+ SetFocusedTextInputClientInternal(client);
+}
- if (old != text_input_client_)
- NotifyTextInputStateChanged(text_input_client_);
+void InputMethodBase::SetStickyFocusedTextInputClient(TextInputClient* client) {
+ is_sticky_text_input_client_ = (client != NULL);
+ SetFocusedTextInputClientInternal(client);
}
void InputMethodBase::DetachTextInputClient(TextInputClient* client) {
if (text_input_client_ == client) {
OnWillChangeFocusedClient(client, NULL);
text_input_client_ = NULL;
+ is_sticky_text_input_client_ = false;
OnDidChangeFocusedClient(client, NULL);
NotifyTextInputStateChanged(text_input_client_);
}
@@ -127,4 +130,15 @@ void InputMethodBase::NotifyTextInputStateChanged(
OnTextInputStateChanged(client));
}
+void InputMethodBase::SetFocusedTextInputClientInternal(
+ TextInputClient* client) {
+ TextInputClient* old = text_input_client_;
+ if (old == client)
+ return;
+ OnWillChangeFocusedClient(old, client);
+ text_input_client_ = client; // NULL allowed.
+ OnDidChangeFocusedClient(old, client);
+ NotifyTextInputStateChanged(text_input_client_);
+}
+
} // namespace ui
diff --git a/ui/base/ime/input_method_base.h b/ui/base/ime/input_method_base.h
index a3a106e..c5dcfd6 100644
--- a/ui/base/ime/input_method_base.h
+++ b/ui/base/ime/input_method_base.h
@@ -37,6 +37,8 @@ class UI_EXPORT InputMethodBase : NON_EXPORTED_BASE(public InputMethod) {
virtual void OnFocus() OVERRIDE;
virtual void OnBlur() OVERRIDE;
virtual void SetFocusedTextInputClient(TextInputClient* client) OVERRIDE;
+ virtual void SetStickyFocusedTextInputClient(
+ TextInputClient* client) OVERRIDE;
virtual void DetachTextInputClient(TextInputClient* client) OVERRIDE;
virtual TextInputClient* GetTextInputClient() const OVERRIDE;
@@ -88,8 +90,11 @@ class UI_EXPORT InputMethodBase : NON_EXPORTED_BASE(public InputMethod) {
}
private:
+ void SetFocusedTextInputClientInternal(TextInputClient* client);
+
internal::InputMethodDelegate* delegate_;
TextInputClient* text_input_client_;
+ bool is_sticky_text_input_client_;
ObserverList<InputMethodObserver> observer_list_;
diff --git a/ui/base/ime/input_method_base_unittest.cc b/ui/base/ime/input_method_base_unittest.cc
new file mode 100644
index 0000000..a0558c2
--- /dev/null
+++ b/ui/base/ime/input_method_base_unittest.cc
@@ -0,0 +1,383 @@
+// Copyright 2013 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 "ui/base/ime/input_method_base.h"
+
+#include "base/scoped_observer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/events/event.h"
+#include "ui/base/ime/dummy_text_input_client.h"
+#include "ui/base/ime/input_method_observer.h"
+
+namespace ui {
+namespace {
+
+class ClientChangeVerifier {
+ public:
+ ClientChangeVerifier()
+ : previous_client_(NULL),
+ next_client_(NULL),
+ call_expected_(false),
+ on_will_change_focused_client_called_(false),
+ on_did_change_focused_client_called_(false),
+ on_text_input_state_changed_(false) {
+ }
+
+ // Expects that focused text input client will not be changed.
+ void ExpectClientDoesNotChange() {
+ previous_client_ = NULL;
+ next_client_ = NULL;
+ call_expected_ = false;
+ on_will_change_focused_client_called_ = false;
+ on_did_change_focused_client_called_ = false;
+ on_text_input_state_changed_ = false;
+ }
+
+ // Expects that focused text input client will be changed from
+ // |previous_client| to |next_client|.
+ void ExpectClientChange(TextInputClient* previous_client,
+ TextInputClient* next_client) {
+ previous_client_ = previous_client;
+ next_client_ = next_client;
+ call_expected_ = true;
+ on_will_change_focused_client_called_ = false;
+ on_did_change_focused_client_called_ = false;
+ on_text_input_state_changed_ = false;
+ }
+
+ // Verifies the result satisfies the expectation or not.
+ void Verify() {
+ EXPECT_EQ(call_expected_, on_will_change_focused_client_called_);
+ EXPECT_EQ(call_expected_, on_did_change_focused_client_called_);
+ EXPECT_EQ(call_expected_, on_text_input_state_changed_);
+ }
+
+ void OnWillChangeFocusedClient(TextInputClient* focused_before,
+ TextInputClient* focused) {
+ EXPECT_TRUE(call_expected_);
+
+ // Check arguments
+ EXPECT_EQ(previous_client_, focused_before);
+ EXPECT_EQ(next_client_, focused);
+
+ // Check call order
+ EXPECT_FALSE(on_will_change_focused_client_called_);
+ EXPECT_FALSE(on_did_change_focused_client_called_);
+ EXPECT_FALSE(on_text_input_state_changed_);
+
+ on_will_change_focused_client_called_ = true;
+ }
+
+ void OnDidChangeFocusedClient(TextInputClient* focused_before,
+ TextInputClient* focused) {
+ EXPECT_TRUE(call_expected_);
+
+ // Check arguments
+ EXPECT_EQ(previous_client_, focused_before);
+ EXPECT_EQ(next_client_, focused);
+
+ // Check call order
+ EXPECT_TRUE(on_will_change_focused_client_called_);
+ EXPECT_FALSE(on_did_change_focused_client_called_);
+ EXPECT_FALSE(on_text_input_state_changed_);
+
+ on_did_change_focused_client_called_ = true;
+ }
+
+ void OnTextInputStateChanged(const TextInputClient* client) {
+ EXPECT_TRUE(call_expected_);
+
+ // Check arguments
+ EXPECT_EQ(next_client_, client);
+
+ // Check call order
+ EXPECT_TRUE(on_will_change_focused_client_called_);
+ EXPECT_TRUE(on_did_change_focused_client_called_);
+ EXPECT_FALSE(on_text_input_state_changed_);
+
+ on_text_input_state_changed_ = true;
+ }
+
+ private:
+ TextInputClient* previous_client_;
+ TextInputClient* next_client_;
+ bool call_expected_;
+ bool on_will_change_focused_client_called_;
+ bool on_did_change_focused_client_called_;
+ bool on_text_input_state_changed_;
+
+ DISALLOW_COPY_AND_ASSIGN(ClientChangeVerifier);
+};
+
+class MockInputMethodBase : public InputMethodBase {
+ public:
+ // Note: this class does not take the ownership of |verifier|.
+ explicit MockInputMethodBase(ClientChangeVerifier* verifier)
+ : verifier_(verifier) {
+ }
+ virtual ~MockInputMethodBase() {
+ }
+
+ private:
+ // Overriden from InputMethod.
+ virtual bool OnUntranslatedIMEMessage(
+ const base::NativeEvent& event,
+ InputMethod::NativeEventResult* result) OVERRIDE {
+ return false;
+ }
+ virtual bool DispatchKeyEvent(const base::NativeEvent&) OVERRIDE {
+ return false;
+ }
+ virtual bool DispatchFabricatedKeyEvent(const ui::KeyEvent&) OVERRIDE {
+ return false;
+ }
+ virtual void OnCaretBoundsChanged(const TextInputClient* clien) OVERRIDE {
+ }
+ virtual void CancelComposition(const TextInputClient* clien) OVERRIDE {
+ }
+ virtual void OnInputLocaleChanged() OVERRIDE {
+ }
+ virtual std::string GetInputLocale() OVERRIDE {
+ return "";
+ }
+ virtual base::i18n::TextDirection GetInputTextDirection() OVERRIDE {
+ return base::i18n::UNKNOWN_DIRECTION;
+ }
+ virtual bool IsActive() OVERRIDE {
+ return false;
+ }
+ virtual bool IsCandidatePopupOpen() const OVERRIDE {
+ return false;
+ }
+
+ // Overriden from InputMethodBase.
+ virtual void OnWillChangeFocusedClient(TextInputClient* focused_before,
+ TextInputClient* focused) OVERRIDE {
+ verifier_->OnWillChangeFocusedClient(focused_before, focused);
+ }
+
+ virtual void OnDidChangeFocusedClient(TextInputClient* focused_before,
+ TextInputClient* focused) OVERRIDE {
+ verifier_->OnDidChangeFocusedClient(focused_before, focused);
+ }
+
+ ClientChangeVerifier* verifier_;
+ DISALLOW_COPY_AND_ASSIGN(MockInputMethodBase);
+};
+
+class MockInputMethodObserver : public InputMethodObserver {
+ public:
+ // Note: this class does not take the ownership of |verifier|.
+ explicit MockInputMethodObserver(ClientChangeVerifier* verifier)
+ : verifier_(verifier) {
+ }
+ virtual ~MockInputMethodObserver() {
+ }
+
+ private:
+ virtual void OnTextInputStateChanged(const TextInputClient* client) OVERRIDE {
+ verifier_->OnTextInputStateChanged(client);
+ }
+ virtual void OnInputMethodDestroyed(const InputMethod* client) OVERRIDE {
+ }
+
+ ClientChangeVerifier* verifier_;
+ DISALLOW_COPY_AND_ASSIGN(MockInputMethodObserver);
+};
+
+typedef ScopedObserver<InputMethod, InputMethodObserver>
+ InputMethodScopedObserver;
+
+TEST(InputMethodBaseTest, SetFocusedTextInputClient) {
+ DummyTextInputClient text_input_client_1st;
+ DummyTextInputClient text_input_client_2nd;
+
+ ClientChangeVerifier verifier;
+ MockInputMethodBase input_method(&verifier);
+ MockInputMethodObserver input_method_observer(&verifier);
+ InputMethodScopedObserver scoped_observer(&input_method_observer);
+ scoped_observer.Add(&input_method);
+
+ // Assume that the top-level-widget gains focus.
+ input_method.OnFocus();
+
+ {
+ SCOPED_TRACE("Focus from NULL to 1st TextInputClient");
+
+ ASSERT_EQ(NULL, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(NULL, &text_input_client_1st);
+ input_method.SetFocusedTextInputClient(&text_input_client_1st);
+ EXPECT_EQ(&text_input_client_1st, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Redundant focus events must be ignored");
+ verifier.ExpectClientDoesNotChange();
+ input_method.SetFocusedTextInputClient(&text_input_client_1st);
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Focus from 1st to 2nd TextInputClient");
+
+ ASSERT_EQ(&text_input_client_1st, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(&text_input_client_1st,
+ &text_input_client_2nd);
+ input_method.SetFocusedTextInputClient(&text_input_client_2nd);
+ EXPECT_EQ(&text_input_client_2nd, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Focus from 2nd TextInputClient to NULL");
+
+ ASSERT_EQ(&text_input_client_2nd, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(&text_input_client_2nd, NULL);
+ input_method.SetFocusedTextInputClient(NULL);
+ EXPECT_EQ(NULL, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Redundant focus events must be ignored");
+ verifier.ExpectClientDoesNotChange();
+ input_method.SetFocusedTextInputClient(NULL);
+ verifier.Verify();
+ }
+}
+
+TEST(InputMethodBaseTest, DetachTextInputClient) {
+ DummyTextInputClient text_input_client;
+ DummyTextInputClient text_input_client_the_other;
+
+ ClientChangeVerifier verifier;
+ MockInputMethodBase input_method(&verifier);
+ MockInputMethodObserver input_method_observer(&verifier);
+ InputMethodScopedObserver scoped_observer(&input_method_observer);
+ scoped_observer.Add(&input_method);
+
+ // Assume that the top-level-widget gains focus.
+ input_method.OnFocus();
+
+ // Initialize for the next test.
+ {
+ verifier.ExpectClientChange(NULL, &text_input_client);
+ input_method.SetFocusedTextInputClient(&text_input_client);
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("DetachTextInputClient must be ignored for other clients");
+ ASSERT_EQ(&text_input_client, input_method.GetTextInputClient());
+ verifier.ExpectClientDoesNotChange();
+ input_method.DetachTextInputClient(&text_input_client_the_other);
+ EXPECT_EQ(&text_input_client, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("DetachTextInputClient must succeed even after the "
+ "top-level loses the focus");
+
+ ASSERT_EQ(&text_input_client, input_method.GetTextInputClient());
+ input_method.OnBlur();
+ input_method.OnFocus();
+ verifier.ExpectClientChange(&text_input_client, NULL);
+ input_method.DetachTextInputClient(&text_input_client);
+ EXPECT_EQ(NULL, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+}
+
+TEST(InputMethodBaseTest, SetStickyFocusedTextInputClient) {
+ DummyTextInputClient sticky_client;
+ DummyTextInputClient non_sticky_client;
+
+ ClientChangeVerifier verifier;
+ MockInputMethodBase input_method(&verifier);
+ MockInputMethodObserver input_method_observer(&verifier);
+ InputMethodScopedObserver scoped_observer(&input_method_observer);
+ scoped_observer.Add(&input_method);
+
+ // Assume that the top-level-widget gains focus.
+ input_method.OnFocus();
+
+ {
+ SCOPED_TRACE("Focus from NULL to non-sticky client");
+
+ ASSERT_EQ(NULL, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(NULL, &non_sticky_client);
+ input_method.SetFocusedTextInputClient(&non_sticky_client);
+ EXPECT_EQ(&non_sticky_client, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Focus from non-sticky to sticky client");
+
+ ASSERT_EQ(&non_sticky_client, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(&non_sticky_client, &sticky_client);
+ input_method.SetStickyFocusedTextInputClient(&sticky_client);
+ EXPECT_EQ(&sticky_client, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Focus from sticky to non-sticky client -> must fail");
+
+ ASSERT_EQ(&sticky_client, input_method.GetTextInputClient());
+ verifier.ExpectClientDoesNotChange();
+ input_method.SetFocusedTextInputClient(&non_sticky_client);
+ EXPECT_EQ(&sticky_client, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Focus from sticky to NULL -> must fail");
+
+ verifier.ExpectClientDoesNotChange();
+ input_method.SetFocusedTextInputClient(NULL);
+ EXPECT_EQ(&sticky_client, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("SetStickyFocusedTextInputClient(NULL) must be supported");
+
+ ASSERT_EQ(&sticky_client, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(&sticky_client, NULL);
+ input_method.SetStickyFocusedTextInputClient(NULL);
+ EXPECT_EQ(NULL, input_method.GetTextInputClient());
+ verifier.Verify();
+
+ // |SetStickyFocusedTextInputClient(NULL)| must be equivalent to
+ // |SetFocusedTextInputClient(NULL)|. We should be able to use
+ // |SetFocusedTextInputClient(&non_sticky_client)| here.
+ verifier.ExpectClientChange(NULL, &non_sticky_client);
+ input_method.SetFocusedTextInputClient(&non_sticky_client);
+ EXPECT_EQ(&non_sticky_client, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("Set stick client again for the next test");
+ verifier.ExpectClientChange(&non_sticky_client, &sticky_client);
+ input_method.SetStickyFocusedTextInputClient(&sticky_client);
+ verifier.Verify();
+ }
+
+ {
+ SCOPED_TRACE("DetachTextInputClient must be supported for sticky client");
+
+ ASSERT_EQ(&sticky_client, input_method.GetTextInputClient());
+ verifier.ExpectClientChange(&sticky_client, NULL);
+ input_method.DetachTextInputClient(&sticky_client);
+ EXPECT_EQ(NULL, input_method.GetTextInputClient());
+ verifier.Verify();
+ }
+}
+
+} // namespace
+} // namespace ui
diff --git a/ui/base/ime/mock_input_method.cc b/ui/base/ime/mock_input_method.cc
index 58134fa..b6323ef 100644
--- a/ui/base/ime/mock_input_method.cc
+++ b/ui/base/ime/mock_input_method.cc
@@ -7,7 +7,8 @@
namespace ui {
MockInputMethod::MockInputMethod(internal::InputMethodDelegate* delegate)
- : text_input_client_(NULL) {
+ : text_input_client_(NULL),
+ is_sticky_text_input_client_(false) {
}
MockInputMethod::~MockInputMethod() {
@@ -17,6 +18,8 @@ void MockInputMethod::SetDelegate(internal::InputMethodDelegate* delegate) {
}
void MockInputMethod::SetFocusedTextInputClient(TextInputClient* client) {
+ if (is_sticky_text_input_client_)
+ return;
if (text_input_client_ == client)
return;
text_input_client_ = client;
@@ -24,9 +27,20 @@ void MockInputMethod::SetFocusedTextInputClient(TextInputClient* client) {
OnTextInputTypeChanged(client);
}
-void MockInputMethod::DetachTextInputClient(TextInputClient* client) {
+void MockInputMethod::SetStickyFocusedTextInputClient(TextInputClient* client) {
+ is_sticky_text_input_client_ = (client != NULL);
if (text_input_client_ == client)
+ return;
+ text_input_client_ = client;
+ if (client)
+ OnTextInputTypeChanged(client);
+}
+
+void MockInputMethod::DetachTextInputClient(TextInputClient* client) {
+ if (text_input_client_ == client) {
text_input_client_ = NULL;
+ is_sticky_text_input_client_ = false;
+ }
}
TextInputClient* MockInputMethod::GetTextInputClient() const {
diff --git a/ui/base/ime/mock_input_method.h b/ui/base/ime/mock_input_method.h
index 1d7922f..8e14988 100644
--- a/ui/base/ime/mock_input_method.h
+++ b/ui/base/ime/mock_input_method.h
@@ -49,6 +49,8 @@ class UI_EXPORT MockInputMethod : NON_EXPORTED_BASE(public InputMethod) {
virtual bool OnUntranslatedIMEMessage(const base::NativeEvent& event,
NativeEventResult* result) OVERRIDE;
virtual void SetFocusedTextInputClient(TextInputClient* client) OVERRIDE;
+ virtual void SetStickyFocusedTextInputClient(
+ TextInputClient* client) OVERRIDE;
virtual void DetachTextInputClient(TextInputClient* client) OVERRIDE;
virtual TextInputClient* GetTextInputClient() const OVERRIDE;
virtual bool DispatchKeyEvent(const base::NativeEvent& native_event) OVERRIDE;
@@ -69,6 +71,7 @@ class UI_EXPORT MockInputMethod : NON_EXPORTED_BASE(public InputMethod) {
private:
TextInputClient* text_input_client_;
+ bool is_sticky_text_input_client_;
ObserverList<Observer> observer_list_;
DISALLOW_COPY_AND_ASSIGN(MockInputMethod);