diff options
author | Yukawa@chromium.org <Yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-13 16:13:35 +0000 |
---|---|---|
committer | Yukawa@chromium.org <Yukawa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-09-13 16:13:35 +0000 |
commit | 38ef5018776ab633f1b2fcafaf456b453a6be8f6 (patch) | |
tree | ad1c108ecae06b755bfe41d4c6699a3976ba40ad | |
parent | 81b4bbd6e5edaa9449c2d48e2ffc5e58318694e1 (diff) | |
download | chromium_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.cc | 4 | ||||
-rw-r--r-- | ui/base/ime/dummy_input_method.h | 2 | ||||
-rw-r--r-- | ui/base/ime/dummy_text_input_client.cc | 101 | ||||
-rw-r--r-- | ui/base/ime/dummy_text_input_client.h | 49 | ||||
-rw-r--r-- | ui/base/ime/fake_input_method.cc | 13 | ||||
-rw-r--r-- | ui/base/ime/fake_input_method.h | 3 | ||||
-rw-r--r-- | ui/base/ime/ime_test_support.gypi | 2 | ||||
-rw-r--r-- | ui/base/ime/ime_unittests.gypi | 18 | ||||
-rw-r--r-- | ui/base/ime/input_method.h | 8 | ||||
-rw-r--r-- | ui/base/ime/input_method_base.cc | 26 | ||||
-rw-r--r-- | ui/base/ime/input_method_base.h | 5 | ||||
-rw-r--r-- | ui/base/ime/input_method_base_unittest.cc | 383 | ||||
-rw-r--r-- | ui/base/ime/mock_input_method.cc | 18 | ||||
-rw-r--r-- | ui/base/ime/mock_input_method.h | 3 |
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); |