diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:20:33 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:28:57 +0000 |
commit | d17ea114e5ef69ad5d5d7413280a13e6428098aa (patch) | |
tree | 2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/ui/base/ime | |
parent | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff) | |
download | qtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz |
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/ui/base/ime')
53 files changed, 5260 insertions, 584 deletions
diff --git a/chromium/ui/base/ime/BUILD.gn b/chromium/ui/base/ime/BUILD.gn index a8a36ead5f3..9e0c6190234 100644 --- a/chromium/ui/base/ime/BUILD.gn +++ b/chromium/ui/base/ime/BUILD.gn @@ -61,8 +61,6 @@ jumbo_component("ime") { "infolist_entry.cc", "infolist_entry.h", "input_method.h", - "input_method_auralinux.cc", - "input_method_auralinux.h", "input_method_base.cc", "input_method_base.h", "input_method_chromeos.cc", @@ -77,8 +75,6 @@ jumbo_component("ime") { "input_method_minimal.cc", "input_method_minimal.h", "input_method_observer.h", - "input_method_win.cc", - "input_method_win.h", "linux/fake_input_method_context.cc", "linux/fake_input_method_context.h", "linux/fake_input_method_context_factory.cc", @@ -96,8 +92,21 @@ jumbo_component("ime") { "ui_base_ime_export.h", "win/imm32_manager.cc", "win/imm32_manager.h", + "win/on_screen_keyboard_display_manager_stub.cc", + "win/on_screen_keyboard_display_manager_stub.h", + "win/on_screen_keyboard_display_manager_tab_tip.cc", + "win/on_screen_keyboard_display_manager_tab_tip.h", + "win/osk_display_manager.cc", + "win/osk_display_manager.h", + "win/osk_display_observer.h", + "win/tsf_bridge.cc", + "win/tsf_bridge.h", + "win/tsf_event_router.cc", + "win/tsf_event_router.h", "win/tsf_input_scope.cc", "win/tsf_input_scope.h", + "win/tsf_text_store.cc", + "win/tsf_text_store.h", ] # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. @@ -112,6 +121,7 @@ jumbo_component("ime") { "//net", "//third_party/icu", "//ui/base", + "//ui/base/ime/chromeos/public/interfaces", "//ui/display", "//ui/events", "//ui/gfx", @@ -125,8 +135,8 @@ jumbo_component("ime") { "//skia", ] - if (!use_aura || is_chromeos || (!is_linux && !use_ozone)) { - sources -= [ + if (is_desktop_linux) { + sources += [ "input_method_auralinux.cc", "input_method_auralinux.h", ] @@ -179,17 +189,25 @@ jumbo_component("ime") { cflags = [ "/wd4324" ] # Structure was padded due to __declspec(align()), which is # uninteresting. + sources += [ + "input_method_win.cc", + "input_method_win.h", + "input_method_win_base.cc", + "input_method_win_base.h", + "input_method_win_tsf.cc", + "input_method_win_tsf.h", + ] libs = [ "imm32.lib" ] + + jumbo_excluded_sources = [ + # tsf_text_store.cc needs INITGUID to be defined before + # including any header to properly generate GUID objects. That + # is not guaranteed when included in a jumbo build. + "win/tsf_text_store.cc", + ] } if (is_mac) { libs = [ "AppKit.framework" ] } - - if (is_fuchsia) { - sources -= [ - "input_method_auralinux.cc", - "input_method_auralinux.h", - ] - } } diff --git a/chromium/ui/base/ime/chromeos/public/interfaces/BUILD.gn b/chromium/ui/base/ime/chromeos/public/interfaces/BUILD.gn new file mode 100644 index 00000000000..625cdd4cf3f --- /dev/null +++ b/chromium/ui/base/ime/chromeos/public/interfaces/BUILD.gn @@ -0,0 +1,11 @@ +# Copyright 2018 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. + +import("//mojo/public/tools/bindings/mojom.gni") + +mojom("interfaces") { + sources = [ + "ime_keyset.mojom", + ] +} diff --git a/chromium/ui/base/ime/chromeos/public/interfaces/ime_keyset.mojom b/chromium/ui/base/ime/chromeos/public/interfaces/ime_keyset.mojom new file mode 100644 index 00000000000..7a9d6297a67 --- /dev/null +++ b/chromium/ui/base/ime/chromeos/public/interfaces/ime_keyset.mojom @@ -0,0 +1,15 @@ +// Copyright 2018 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. + +module chromeos.input_method.mojom; + +// Used by the virtual keyboard to represent different key layouts for +// different purposes. 'kNone' represents the default key layout. +// Used in UMA, so this enum should not be reordered. +enum ImeKeyset { + kNone = 0, + kEmoji = 1, + kHandwriting = 2, + kVoice = 3, +}; diff --git a/chromium/ui/base/ime/composition_text_unittest.cc b/chromium/ui/base/ime/composition_text_unittest.cc index 8431e2eb6d5..9c57c233388 100644 --- a/chromium/ui/base/ime/composition_text_unittest.cc +++ b/chromium/ui/base/ime/composition_text_unittest.cc @@ -14,14 +14,17 @@ namespace ui { TEST(CompositionTextTest, CopyTest) { const base::string16 kSampleText = base::UTF8ToUTF16("Sample Text"); const ImeTextSpan kSampleUnderline1(ImeTextSpan::Type::kComposition, 10, 20, - SK_ColorBLACK, false, + ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT); const ImeTextSpan kSampleUnderline2(ImeTextSpan::Type::kComposition, 11, 21, - SK_ColorBLACK, true, SK_ColorTRANSPARENT); + ImeTextSpan::Thickness::kThick, + SK_ColorTRANSPARENT); - const ImeTextSpan kSampleUnderline3(ImeTextSpan::Type::kComposition, 12, 22, - SK_ColorRED, false, SK_ColorTRANSPARENT); + ImeTextSpan kSampleUnderline3(ImeTextSpan::Type::kComposition, 12, 22, + ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT); + kSampleUnderline3.underline_color = SK_ColorRED; // Make CompositionText CompositionText text; @@ -43,7 +46,8 @@ TEST(CompositionTextTest, CopyTest) { text2.ime_text_spans[i].end_offset); EXPECT_EQ(text.ime_text_spans[i].underline_color, text2.ime_text_spans[i].underline_color); - EXPECT_EQ(text.ime_text_spans[i].thick, text2.ime_text_spans[i].thick); + EXPECT_EQ(text.ime_text_spans[i].thickness, + text2.ime_text_spans[i].thickness); EXPECT_EQ(text.ime_text_spans[i].background_color, text2.ime_text_spans[i].background_color); } diff --git a/chromium/ui/base/ime/composition_text_util_pango.cc b/chromium/ui/base/ime/composition_text_util_pango.cc index 9ef58037f7b..e5980f367a0 100644 --- a/chromium/ui/base/ime/composition_text_util_pango.cc +++ b/chromium/ui/base/ime/composition_text_util_pango.cc @@ -74,15 +74,16 @@ void ExtractCompositionTextFromGtkPreedit(const gchar* utf8_text, pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE); if (background_attr || underline_attr) { - // Use a black thin underline by default. + // Use a thin underline with text color by default. ImeTextSpan ime_text_span(ImeTextSpan::Type::kComposition, char16_offsets[start], char16_offsets[end], - SK_ColorBLACK, false, SK_ColorTRANSPARENT); + ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT); // Always use thick underline for a range with background color, which // is usually the selection range. if (background_attr) { - ime_text_span.thick = true; + ime_text_span.thickness = ImeTextSpan::Thickness::kThick; // If the cursor is at start or end of this underline, then we treat // it as the selection range as well, but make sure to set the cursor // position to the selection end. @@ -97,7 +98,7 @@ void ExtractCompositionTextFromGtkPreedit(const gchar* utf8_text, if (underline_attr) { int type = reinterpret_cast<PangoAttrInt*>(underline_attr)->value; if (type == PANGO_UNDERLINE_DOUBLE) - ime_text_span.thick = true; + ime_text_span.thickness = ImeTextSpan::Thickness::kThick; else if (type == PANGO_UNDERLINE_ERROR) ime_text_span.underline_color = SK_ColorRED; } @@ -107,11 +108,11 @@ void ExtractCompositionTextFromGtkPreedit(const gchar* utf8_text, pango_attr_iterator_destroy(iter); } - // Use a black thin underline by default. + // Use a thin underline with text color by default. if (composition->ime_text_spans.empty()) { composition->ime_text_spans.push_back( - ImeTextSpan(ImeTextSpan::Type::kComposition, 0, length, SK_ColorBLACK, - false, SK_ColorTRANSPARENT)); + ImeTextSpan(ImeTextSpan::Type::kComposition, 0, length, + ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT)); } } diff --git a/chromium/ui/base/ime/composition_text_util_pango_unittest.cc b/chromium/ui/base/ime/composition_text_util_pango_unittest.cc index be674433b65..26e34395208 100644 --- a/chromium/ui/base/ime/composition_text_util_pango_unittest.cc +++ b/chromium/ui/base/ime/composition_text_util_pango_unittest.cc @@ -29,7 +29,7 @@ struct ImeTextSpan { unsigned start_offset; unsigned end_offset; uint32_t underline_color; - bool thick; + ui::ImeTextSpan::Thickness thickness; uint32_t background_color; }; @@ -47,10 +47,13 @@ const TestData kTestData[] = { {PANGO_ATTR_BACKGROUND, 0, 4, 7}, {PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 8, 13}, {0, 0, 0, 0}}, - {{0, 3, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {4, 7, SK_ColorBLACK, true, SK_ColorTRANSPARENT}, - {8, 13, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {0, 0, 0, false, SK_ColorTRANSPARENT}}}, + {{0, 3, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {4, 7, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThick, + SK_ColorTRANSPARENT}, + {8, 13, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {0, 0, 0, ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT}}}, // Offset overflow. {"One Two Three", @@ -58,10 +61,13 @@ const TestData kTestData[] = { {PANGO_ATTR_BACKGROUND, 0, 4, 7}, {PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 8, 20}, {0, 0, 0, 0}}, - {{0, 3, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {4, 7, SK_ColorBLACK, true, SK_ColorTRANSPARENT}, - {8, 13, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {0, 0, 0, false, SK_ColorTRANSPARENT}}}, + {{0, 3, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {4, 7, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThick, + SK_ColorTRANSPARENT}, + {8, 13, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {0, 0, 0, ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT}}}, // Error underline. {"One Two Three", @@ -69,16 +75,20 @@ const TestData kTestData[] = { {PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_ERROR, 4, 7}, {PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 8, 13}, {0, 0, 0, 0}}, - {{0, 3, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {4, 7, SK_ColorRED, false, SK_ColorTRANSPARENT}, - {8, 13, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {0, 0, 0, false, SK_ColorTRANSPARENT}}}, + {{0, 3, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {4, 7, SK_ColorRED, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {8, 13, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {0, 0, 0, ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT}}}, // Default underline. {"One Two Three", {{0, 0, 0, 0}}, - {{0, 13, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {0, 0, 0, false, SK_ColorTRANSPARENT}}}, + {{0, 13, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {0, 0, 0, ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT}}}, // Unicode, including non-BMP characters: "123你好𠀀𠀁一丁 456" {"123\xE4\xBD\xA0\xE5\xA5\xBD\xF0\xA0\x80\x80\xF0\xA0\x80\x81\xE4\xB8\x80" @@ -88,18 +98,22 @@ const TestData kTestData[] = { {PANGO_ATTR_BACKGROUND, 0, 5, 7}, {PANGO_ATTR_UNDERLINE, PANGO_UNDERLINE_SINGLE, 7, 13}, {0, 0, 0, 0}}, - {{0, 3, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {3, 5, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {5, 9, SK_ColorBLACK, true, SK_ColorTRANSPARENT}, - {9, 15, SK_ColorBLACK, false, SK_ColorTRANSPARENT}, - {0, 0, 0, false, SK_ColorTRANSPARENT}}}, + {{0, 3, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {3, 5, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {5, 9, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThick, + SK_ColorTRANSPARENT}, + {9, 15, SK_ColorTRANSPARENT, ui::ImeTextSpan::Thickness::kThin, + SK_ColorTRANSPARENT}, + {0, 0, 0, ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT}}}, }; void CompareImeTextSpan(const ImeTextSpan& a, const ui::ImeTextSpan& b) { EXPECT_EQ(a.start_offset, b.start_offset); EXPECT_EQ(a.end_offset, b.end_offset); EXPECT_EQ(a.underline_color, b.underline_color); - EXPECT_EQ(a.thick, b.thick); + EXPECT_EQ(a.thickness, b.thickness); EXPECT_EQ(a.background_color, b.background_color); } diff --git a/chromium/ui/base/ime/dummy_input_method.cc b/chromium/ui/base/ime/dummy_input_method.cc index 9b6b2cdf89b..cda3ff2221e 100644 --- a/chromium/ui/base/ime/dummy_input_method.cc +++ b/chromium/ui/base/ime/dummy_input_method.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "ui/base/ime/dummy_input_method.h" +#include "build/build_config.h" #include "ui/events/event.h" namespace ui { @@ -22,10 +23,12 @@ void DummyInputMethod::OnFocus() { void DummyInputMethod::OnBlur() { } -bool DummyInputMethod::OnUntranslatedIMEMessage(const base::NativeEvent& event, +#if defined(OS_WIN) +bool DummyInputMethod::OnUntranslatedIMEMessage(const MSG event, NativeEventResult* result) { return false; } +#endif void DummyInputMethod::SetFocusedTextInputClient(TextInputClient* client) { } diff --git a/chromium/ui/base/ime/dummy_input_method.h b/chromium/ui/base/ime/dummy_input_method.h index 6a9201f484f..8b41021b831 100644 --- a/chromium/ui/base/ime/dummy_input_method.h +++ b/chromium/ui/base/ime/dummy_input_method.h @@ -6,6 +6,7 @@ #define UI_BASE_IME_DUMMY_INPUT_METHOD_H_ #include "base/macros.h" +#include "build/build_config.h" #include "ui/base/ime/input_method.h" namespace ui { @@ -21,8 +22,12 @@ class DummyInputMethod : public InputMethod { void SetDelegate(internal::InputMethodDelegate* delegate) override; void OnFocus() override; void OnBlur() override; - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, + +#if defined(OS_WIN) + bool OnUntranslatedIMEMessage(const MSG event, NativeEventResult* result) override; +#endif + void SetFocusedTextInputClient(TextInputClient* client) override; void DetachTextInputClient(TextInputClient* client) override; TextInputClient* GetTextInputClient() const override; diff --git a/chromium/ui/base/ime/ime_engine_handler_interface.h b/chromium/ui/base/ime/ime_engine_handler_interface.h index 3987085386a..a226fa38b6e 100644 --- a/chromium/ui/base/ime/ime_engine_handler_interface.h +++ b/chromium/ui/base/ime/ime_engine_handler_interface.h @@ -28,7 +28,7 @@ class KeyEvent; // A interface to handle the engine handler method call. class UI_BASE_IME_EXPORT IMEEngineHandlerInterface { public: - typedef base::Callback<void(bool consumed)> KeyEventDoneCallback; + typedef base::OnceCallback<void(bool consumed)> KeyEventDoneCallback; // A information about a focused text input field. // A type of each member is based on the html spec, but InputContext can be @@ -74,7 +74,7 @@ class UI_BASE_IME_EXPORT IMEEngineHandlerInterface { // Called when the key event is received. // Actual implementation must call |callback| after key event handling. virtual void ProcessKeyEvent(const KeyEvent& key_event, - KeyEventDoneCallback& callback) = 0; + KeyEventDoneCallback callback) = 0; // Called when a new surrounding text is set. The |text| is surrounding text // and |cursor_pos| is 0 based index of cursor position in |text|. If there is diff --git a/chromium/ui/base/ime/ime_text_span.cc b/chromium/ui/base/ime/ime_text_span.cc index 1ee2876be35..14b482ed591 100644 --- a/chromium/ui/base/ime/ime_text_span.cc +++ b/chromium/ui/base/ime/ime_text_span.cc @@ -9,32 +9,28 @@ namespace ui { -ImeTextSpan::ImeTextSpan() : ImeTextSpan(0, 0, SK_ColorTRANSPARENT, false) {} +ImeTextSpan::ImeTextSpan() : ImeTextSpan(0, 0, Thickness::kThin) {} ImeTextSpan::ImeTextSpan(uint32_t start_offset, uint32_t end_offset, - SkColor underline_color, - bool thick) + Thickness thickness) : ImeTextSpan(Type::kComposition, start_offset, end_offset, - underline_color, - thick, + thickness, SK_ColorTRANSPARENT) {} ImeTextSpan::ImeTextSpan(Type type, uint32_t start_offset, uint32_t end_offset, - SkColor underline_color, - bool thick, + Thickness thickness, SkColor background_color, SkColor suggestion_highlight_color, const std::vector<std::string>& suggestions) : type(type), start_offset(start_offset), end_offset(end_offset), - underline_color(underline_color), - thick(thick), + thickness(thickness), background_color(background_color), suggestion_highlight_color(suggestion_highlight_color), suggestions(suggestions) {} diff --git a/chromium/ui/base/ime/ime_text_span.h b/chromium/ui/base/ime/ime_text_span.h index eee554f4fe2..e47064a1a63 100644 --- a/chromium/ui/base/ime/ime_text_span.h +++ b/chromium/ui/base/ime/ime_text_span.h @@ -31,19 +31,23 @@ struct UI_BASE_IME_EXPORT ImeTextSpan { kMisspellingSuggestion, }; + enum class Thickness { + kNone, + kThin, + kThick, + }; + // The default constructor is used by generated Mojo code. ImeTextSpan(); // TODO(huangs): remove this constructor. ImeTextSpan(uint32_t start_offset, uint32_t end_offset, - SkColor underline_color, - bool thick); + Thickness thickness); ImeTextSpan( Type type, uint32_t start_offset, uint32_t end_offset, - SkColor underline_color, - bool thick, + Thickness thickness, SkColor background_color, SkColor suggestion_highlight_color = SK_ColorTRANSPARENT, const std::vector<std::string>& suggestions = std::vector<std::string>()); @@ -57,7 +61,7 @@ struct UI_BASE_IME_EXPORT ImeTextSpan { (this->start_offset == rhs.start_offset) && (this->end_offset == rhs.end_offset) && (this->underline_color == rhs.underline_color) && - (this->thick == rhs.thick) && + (this->thickness == rhs.thickness) && (this->background_color == rhs.background_color) && (this->suggestion_highlight_color == rhs.suggestion_highlight_color) && @@ -69,8 +73,8 @@ struct UI_BASE_IME_EXPORT ImeTextSpan { Type type; uint32_t start_offset; uint32_t end_offset; - SkColor underline_color; - bool thick; + SkColor underline_color = SK_ColorTRANSPARENT; + Thickness thickness; SkColor background_color; SkColor suggestion_highlight_color; std::vector<std::string> suggestions; diff --git a/chromium/ui/base/ime/input_method.h b/chromium/ui/base/ime/input_method.h index fad2ecca3bb..1a5fb7adbd7 100644 --- a/chromium/ui/base/ime/input_method.h +++ b/chromium/ui/base/ime/input_method.h @@ -11,11 +11,11 @@ #include <string> #include <vector> -#include "base/event_types.h" #include "build/build_config.h" #include "ui/base/ime/text_input_mode.h" #include "ui/base/ime/text_input_type.h" #include "ui/events/event_dispatcher.h" +#include "ui/events/platform_event.h" #include "ui/gfx/geometry/rect.h" namespace extensions { @@ -76,12 +76,13 @@ class InputMethod { // Called when the top-level system window loses keyboard focus. virtual void OnBlur() = 0; +#if defined(OS_WIN) // Called when the focused window receives native IME messages that are not // translated into other predefined event callbacks. Currently this method is // used only for IME functionalities specific to Windows. - // TODO(ime): Break down these messages into platform-neutral methods. - virtual bool OnUntranslatedIMEMessage(const base::NativeEvent& event, + virtual bool OnUntranslatedIMEMessage(const MSG event, NativeEventResult* result) = 0; +#endif // Sets the text input client which receives text input events such as // SetCompositionText(). |client| can be NULL. A gfx::NativeWindow which diff --git a/chromium/ui/base/ime/input_method_auralinux.cc b/chromium/ui/base/ime/input_method_auralinux.cc index 9a6c7bee4fc..c02def8d3f7 100644 --- a/chromium/ui/base/ime/input_method_auralinux.cc +++ b/chromium/ui/base/ime/input_method_auralinux.cc @@ -46,12 +46,6 @@ LinuxInputMethodContext* InputMethodAuraLinux::GetContextForTesting( // Overriden from InputMethod. -bool InputMethodAuraLinux::OnUntranslatedIMEMessage( - const base::NativeEvent& event, - NativeEventResult* result) { - return false; -} - ui::EventDispatchDetails InputMethodAuraLinux::DispatchKeyEvent( ui::KeyEvent* event) { DCHECK(event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED); @@ -99,13 +93,14 @@ ui::EventDispatchDetails InputMethodAuraLinux::DispatchKeyEvent( if (text_input_type_ != TEXT_INPUT_TYPE_PASSWORD && GetEngine() && GetEngine()->IsInterestedInKeyEvent() && (!filtered || NeedInsertChar())) { - ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = base::Bind( - &InputMethodAuraLinux::ProcessKeyEventByEngineDone, - weak_ptr_factory_.GetWeakPtr(), base::Owned(new ui::KeyEvent(*event)), - filtered, composition_changed_, - base::Owned(new ui::CompositionText(composition_)), - base::Owned(new base::string16(result_text_))); - GetEngine()->ProcessKeyEvent(*event, callback); + ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = + base::BindOnce(&InputMethodAuraLinux::ProcessKeyEventByEngineDone, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(new ui::KeyEvent(*event)), filtered, + composition_changed_, + base::Owned(new ui::CompositionText(composition_)), + base::Owned(new base::string16(result_text_))); + GetEngine()->ProcessKeyEvent(*event, std::move(callback)); return ui::EventDispatchDetails(); } diff --git a/chromium/ui/base/ime/input_method_auralinux.h b/chromium/ui/base/ime/input_method_auralinux.h index 7a2adae8cf4..ef4985b95c6 100644 --- a/chromium/ui/base/ime/input_method_auralinux.h +++ b/chromium/ui/base/ime/input_method_auralinux.h @@ -27,8 +27,6 @@ class UI_BASE_IME_EXPORT InputMethodAuraLinux LinuxInputMethodContext* GetContextForTesting(bool is_simple); // Overriden from InputMethod. - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, - NativeEventResult* result) override; ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override; void OnTextInputTypeChanged(const TextInputClient* client) override; void OnCaretBoundsChanged(const TextInputClient* client) override; diff --git a/chromium/ui/base/ime/input_method_base.cc b/chromium/ui/base/ime/input_method_base.cc index 7d4747b5c05..ea0e64490e5 100644 --- a/chromium/ui/base/ime/input_method_base.cc +++ b/chromium/ui/base/ime/input_method_base.cc @@ -8,6 +8,7 @@ #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" #include "ui/base/ime/ime_bridge.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/base/ime/input_method_observer.h" @@ -22,9 +23,9 @@ ui::IMEEngineHandlerInterface* InputMethodBase::GetEngine() { return nullptr; } -InputMethodBase::InputMethodBase() +InputMethodBase::InputMethodBase(internal::InputMethodDelegate* delegate) : sending_key_event_(false), - delegate_(nullptr), + delegate_(delegate), text_input_client_(nullptr) {} InputMethodBase::~InputMethodBase() { @@ -53,6 +54,14 @@ void InputMethodBase::OnBlur() { ui::IMEBridge::Get()->SetInputContextHandler(nullptr); } +#if defined(OS_WIN) +bool InputMethodBase::OnUntranslatedIMEMessage( + const MSG event, + InputMethod::NativeEventResult* result) { + return false; +} +#endif + void InputMethodBase::SetFocusedTextInputClient(TextInputClient* client) { SetFocusedTextInputClientInternal(client); } @@ -143,17 +152,17 @@ ui::EventDispatchDetails InputMethodBase::DispatchKeyEventPostIME( ui::EventDispatchDetails InputMethodBase::DispatchKeyEventPostIME( ui::KeyEvent* event, - std::unique_ptr<base::OnceCallback<void(bool)>> ack_callback) const { + base::OnceCallback<void(bool)> ack_callback) const { if (delegate_) { ui::EventDispatchDetails details = delegate_->DispatchKeyEventPostIME(event); - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(event->stopped_propagation()); + if (ack_callback) + std::move(ack_callback).Run(event->stopped_propagation()); return details; } - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(false); + if (ack_callback) + std::move(ack_callback).Run(false); return EventDispatchDetails(); } diff --git a/chromium/ui/base/ime/input_method_base.h b/chromium/ui/base/ime/input_method_base.h index 6a26900d342..f97938b4213 100644 --- a/chromium/ui/base/ime/input_method_base.h +++ b/chromium/ui/base/ime/input_method_base.h @@ -11,6 +11,7 @@ #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/observer_list.h" +#include "build/build_config.h" #include "ui/base/ime/ime_input_context_handler_interface.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/ui_base_ime_export.h" @@ -34,13 +35,19 @@ class UI_BASE_IME_EXPORT InputMethodBase public base::SupportsWeakPtr<InputMethodBase>, public IMEInputContextHandlerInterface { public: - InputMethodBase(); + explicit InputMethodBase(internal::InputMethodDelegate* delegate = nullptr); ~InputMethodBase() override; // Overriden from InputMethod. void SetDelegate(internal::InputMethodDelegate* delegate) override; void OnFocus() override; void OnBlur() override; + +#if defined(OS_WIN) + bool OnUntranslatedIMEMessage(const MSG event, + NativeEventResult* result) override; +#endif + void SetFocusedTextInputClient(TextInputClient* client) override; void DetachTextInputClient(TextInputClient* client) override; TextInputClient* GetTextInputClient() const override; @@ -100,8 +107,7 @@ class UI_BASE_IME_EXPORT InputMethodBase virtual ui::EventDispatchDetails DispatchKeyEventPostIME( ui::KeyEvent* event, - std::unique_ptr<base::OnceCallback<void(bool)>> ack_callback) const - WARN_UNUSED_RESULT; + base::OnceCallback<void(bool)> ack_callback) const WARN_UNUSED_RESULT; // Convenience method to notify all observers of TextInputClient changes. void NotifyTextInputStateChanged(const TextInputClient* client); diff --git a/chromium/ui/base/ime/input_method_base_unittest.cc b/chromium/ui/base/ime/input_method_base_unittest.cc index cccce2d70f5..d5f7dd8936a 100644 --- a/chromium/ui/base/ime/input_method_base_unittest.cc +++ b/chromium/ui/base/ime/input_method_base_unittest.cc @@ -140,11 +140,6 @@ class MockInputMethodBase : public InputMethodBase { private: // Overriden from InputMethod. - bool OnUntranslatedIMEMessage( - const base::NativeEvent& event, - InputMethod::NativeEventResult* result) override { - return false; - } ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent*) override { return ui::EventDispatchDetails(); } diff --git a/chromium/ui/base/ime/input_method_chromeos.cc b/chromium/ui/base/ime/input_method_chromeos.cc index fba861cd4a9..b11f08eaad0 100644 --- a/chromium/ui/base/ime/input_method_chromeos.cc +++ b/chromium/ui/base/ime/input_method_chromeos.cc @@ -15,7 +15,6 @@ #include "base/bind.h" #include "base/i18n/char_iterator.h" #include "base/logging.h" -#include "base/memory/ptr_util.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/third_party/icu/icu_utf.h" @@ -105,8 +104,7 @@ ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent( // TODO(shuchen): Eventually, the language input keys should be handed // over to the IME extension to process. And IMF can handle if the IME // extension didn't handle. - return DispatchKeyEventPostIME( - event, std::make_unique<AckCallback>(std::move(ack_callback))); + return DispatchKeyEventPostIME(event, std::move(ack_callback)); } } } @@ -121,36 +119,28 @@ ui::EventDispatchDetails InputMethodChromeOS::DispatchKeyEvent( if (ExecuteCharacterComposer(*event)) { // Treating as PostIME event if character composer handles key event and // generates some IME event, - return ProcessKeyEventPostIME( - event, std::make_unique<AckCallback>(std::move(ack_callback)), - false, true); + return ProcessKeyEventPostIME(event, std::move(ack_callback), false, + true); } - return ProcessUnfilteredKeyPressEvent( - event, std::make_unique<AckCallback>(std::move(ack_callback))); + return ProcessUnfilteredKeyPressEvent(event, std::move(ack_callback)); } - return DispatchKeyEventPostIME( - event, std::make_unique<AckCallback>(std::move(ack_callback))); + return DispatchKeyEventPostIME(event, std::move(ack_callback)); } handling_key_event_ = true; if (GetEngine()->IsInterestedInKeyEvent()) { - ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = base::Bind( - &InputMethodChromeOS::KeyEventDoneCallback, - weak_ptr_factory_.GetWeakPtr(), - // Pass the ownership of the new copied event. - base::Owned(new ui::KeyEvent(*event)), Passed(&ack_callback)); - GetEngine()->ProcessKeyEvent(*event, callback); + ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = + base::BindOnce(&InputMethodChromeOS::KeyEventDoneCallback, + weak_ptr_factory_.GetWeakPtr(), + // Pass the ownership of the new copied event. + base::Owned(new ui::KeyEvent(*event)), + std::move(ack_callback)); + GetEngine()->ProcessKeyEvent(*event, std::move(callback)); return ui::EventDispatchDetails(); } return ProcessKeyEventDone(event, std::move(ack_callback), false); } -bool InputMethodChromeOS::OnUntranslatedIMEMessage( - const base::NativeEvent& event, - NativeEventResult* result) { - return false; -} - void InputMethodChromeOS::KeyEventDoneCallback(ui::KeyEvent* event, AckCallback ack_callback, bool is_handled) { @@ -176,9 +166,8 @@ ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventDone( } ui::EventDispatchDetails details; if (event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED) { - details = ProcessKeyEventPostIME( - event, std::make_unique<AckCallback>(std::move(ack_callback)), false, - is_handled); + details = ProcessKeyEventPostIME(event, std::move(ack_callback), false, + is_handled); } handling_key_event_ = false; return details; @@ -361,7 +350,7 @@ void InputMethodChromeOS::UpdateContextFocusState() { ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventPostIME( ui::KeyEvent* event, - std::unique_ptr<AckCallback> ack_callback, + AckCallback ack_callback, bool skip_process_filtered, bool handled) { TextInputClient* client = GetTextInputClient(); @@ -378,8 +367,8 @@ ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventPostIME( // In case the focus was changed by the key event. The |context_| should have // been reset when the focused window changed. if (client != GetTextInputClient()) { - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(false); + if (ack_callback) + std::move(ack_callback).Run(false); return dispatch_details; } if (HasInputMethodResult()) @@ -388,14 +377,14 @@ ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventPostIME( // In case the focus was changed when sending input method results to the // focused window. if (client != GetTextInputClient()) { - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(false); + if (ack_callback) + std::move(ack_callback).Run(false); return dispatch_details; } if (handled) { - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(true); + if (ack_callback) + std::move(ack_callback).Run(true); return dispatch_details; // IME handled the key event. do not forward. } @@ -409,11 +398,11 @@ ui::EventDispatchDetails InputMethodChromeOS::ProcessKeyEventPostIME( ui::EventDispatchDetails InputMethodChromeOS::ProcessFilteredKeyPressEvent( ui::KeyEvent* event, - std::unique_ptr<AckCallback> ack_callback) { - auto callback = std::make_unique<AckCallback>(base::Bind( + AckCallback ack_callback) { + auto callback = base::Bind( &InputMethodChromeOS::PostProcessFilteredKeyPressEvent, weak_ptr_factory_.GetWeakPtr(), base::Owned(new ui::KeyEvent(*event)), - GetTextInputClient(), Passed(&ack_callback))); + GetTextInputClient(), Passed(&ack_callback)); if (NeedInsertChar()) return DispatchKeyEventPostIME(event, std::move(callback)); @@ -434,7 +423,7 @@ ui::EventDispatchDetails InputMethodChromeOS::ProcessFilteredKeyPressEvent( void InputMethodChromeOS::PostProcessFilteredKeyPressEvent( ui::KeyEvent* event, TextInputClient* prev_client, - std::unique_ptr<AckCallback> ack_callback, + AckCallback ack_callback, bool stopped_propagation) { // In case the focus was changed by the key event. if (GetTextInputClient() != prev_client) @@ -442,8 +431,8 @@ void InputMethodChromeOS::PostProcessFilteredKeyPressEvent( if (stopped_propagation) { ResetContext(); - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(true); + if (ack_callback) + std::move(ack_callback).Run(true); return; } ignore_result( @@ -452,24 +441,24 @@ void InputMethodChromeOS::PostProcessFilteredKeyPressEvent( ui::EventDispatchDetails InputMethodChromeOS::ProcessUnfilteredKeyPressEvent( ui::KeyEvent* event, - std::unique_ptr<AckCallback> ack_callback) { + AckCallback ack_callback) { return DispatchKeyEventPostIME( event, - std::make_unique<AckCallback>(base::Bind( - &InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent, - weak_ptr_factory_.GetWeakPtr(), base::Owned(new ui::KeyEvent(*event)), - GetTextInputClient(), Passed(&ack_callback)))); + base::BindOnce(&InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(new ui::KeyEvent(*event)), + GetTextInputClient(), std::move(ack_callback))); } void InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent( ui::KeyEvent* event, TextInputClient* prev_client, - std::unique_ptr<AckCallback> ack_callback, + AckCallback ack_callback, bool stopped_propagation) { if (stopped_propagation) { ResetContext(); - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(false); + if (ack_callback) + std::move(ack_callback).Run(false); return; } @@ -482,8 +471,8 @@ void InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent( // We should return here not to send the Tab key event to RWHV. TextInputClient* client = GetTextInputClient(); if (!client || client != prev_client) { - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(false); + if (ack_callback) + std::move(ack_callback).Run(false); return; } @@ -494,8 +483,8 @@ void InputMethodChromeOS::PostProcessUnfilteredKeyPressEvent( if (ch) client->InsertChar(*event); - if (ack_callback && !ack_callback->is_null()) - std::move(*ack_callback).Run(false); + if (ack_callback) + std::move(ack_callback).Run(false); } void InputMethodChromeOS::ProcessInputMethodResult(ui::KeyEvent* event, @@ -703,9 +692,9 @@ void InputMethodChromeOS::ExtractCompositionText( continue; ImeTextSpan ime_text_span(ui::ImeTextSpan::Type::kComposition, char16_offsets[start], char16_offsets[end], - text_ime_text_spans[i].underline_color, - text_ime_text_spans[i].thick, + text_ime_text_spans[i].thickness, text_ime_text_spans[i].background_color); + ime_text_span.underline_color = text_ime_text_spans[i].underline_color; out_composition->ime_text_spans.push_back(ime_text_span); } } @@ -716,7 +705,7 @@ void InputMethodChromeOS::ExtractCompositionText( const uint32_t end = text.selection.end(); ImeTextSpan ime_text_span(ui::ImeTextSpan::Type::kComposition, char16_offsets[start], char16_offsets[end], - SK_ColorBLACK, true /* thick */, + ui::ImeTextSpan::Thickness::kThick, SK_ColorTRANSPARENT); out_composition->ime_text_spans.push_back(ime_text_span); @@ -732,11 +721,11 @@ void InputMethodChromeOS::ExtractCompositionText( } } - // Use a black thin underline by default. + // Use a thin underline with text color by default. if (out_composition->ime_text_spans.empty()) { out_composition->ime_text_spans.push_back( ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, length, - SK_ColorBLACK, false /* thick */, SK_ColorTRANSPARENT)); + ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT)); } } diff --git a/chromium/ui/base/ime/input_method_chromeos.h b/chromium/ui/base/ime/input_method_chromeos.h index 24859210c03..c86e65c66a4 100644 --- a/chromium/ui/base/ime/input_method_chromeos.h +++ b/chromium/ui/base/ime/input_method_chromeos.h @@ -33,8 +33,6 @@ class UI_BASE_IME_EXPORT InputMethodChromeOS : public InputMethodBase { AckCallback ack_callback); // Overridden from InputMethod: - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, - NativeEventResult* result) override; ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override; void OnTextInputTypeChanged(const TextInputClient* client) override; void OnCaretBoundsChanged(const TextInputClient* client) override; @@ -50,7 +48,7 @@ class UI_BASE_IME_EXPORT InputMethodChromeOS : public InputMethodBase { // Process a key returned from the input method. virtual ui::EventDispatchDetails ProcessKeyEventPostIME( ui::KeyEvent* event, - std::unique_ptr<AckCallback> ack_callback, + AckCallback ack_callback, bool skip_process_filtered, bool handled) WARN_UNUSED_RESULT; @@ -79,26 +77,24 @@ class UI_BASE_IME_EXPORT InputMethodChromeOS : public InputMethodBase { // when dispatching post IME. ui::EventDispatchDetails ProcessFilteredKeyPressEvent( ui::KeyEvent* event, - std::unique_ptr<AckCallback> ack_callback) WARN_UNUSED_RESULT; + AckCallback ack_callback) WARN_UNUSED_RESULT; // Post processes a key event that was already filtered by the input method. - void PostProcessFilteredKeyPressEvent( - ui::KeyEvent* event, - TextInputClient* prev_client, - std::unique_ptr<AckCallback> ack_callback, - bool stopped_propagation); + void PostProcessFilteredKeyPressEvent(ui::KeyEvent* event, + TextInputClient* prev_client, + AckCallback ack_callback, + bool stopped_propagation); // Processes a key event that was not filtered by the input method. ui::EventDispatchDetails ProcessUnfilteredKeyPressEvent( ui::KeyEvent* event, - std::unique_ptr<AckCallback> ack_callback) WARN_UNUSED_RESULT; + AckCallback ack_callback) WARN_UNUSED_RESULT; // Post processes a key event that was unfiltered by the input method. - void PostProcessUnfilteredKeyPressEvent( - ui::KeyEvent* event, - TextInputClient* prev_client, - std::unique_ptr<AckCallback> ack_callback, - bool stopped_propagation); + void PostProcessUnfilteredKeyPressEvent(ui::KeyEvent* event, + TextInputClient* prev_client, + AckCallback ack_callback, + bool stopped_propagation); // Sends input method result caused by the given key event to the focused text // input client. diff --git a/chromium/ui/base/ime/input_method_chromeos_unittest.cc b/chromium/ui/base/ime/input_method_chromeos_unittest.cc index fc127d28125..c826c1f150e 100644 --- a/chromium/ui/base/ime/input_method_chromeos_unittest.cc +++ b/chromium/ui/base/ime/input_method_chromeos_unittest.cc @@ -51,11 +51,6 @@ uint32_t GetOffsetInUTF16(const base::string16& utf16_string, return char_iterator.array_pos(); } -enum KeyEventHandlerBehavior { - KEYEVENT_CONSUME, - KEYEVENT_NOT_CONSUME, -}; - } // namespace @@ -67,22 +62,23 @@ class TestableInputMethodChromeOS : public InputMethodChromeOS { } struct ProcessKeyEventPostIMEArgs { - ProcessKeyEventPostIMEArgs() : event(NULL), handled(false) {} - const ui::KeyEvent* event; + ProcessKeyEventPostIMEArgs() + : event(ET_UNKNOWN, VKEY_UNKNOWN, DomCode::NONE, EF_NONE), + handled(false) {} + ui::KeyEvent event; bool handled; }; // Overridden from InputMethodChromeOS: - ui::EventDispatchDetails ProcessKeyEventPostIME( - ui::KeyEvent* key_event, - std::unique_ptr<AckCallback> ack_callback, - bool skip_process_filtered, - bool handled) override { + ui::EventDispatchDetails ProcessKeyEventPostIME(ui::KeyEvent* key_event, + AckCallback ack_callback, + bool skip_process_filtered, + bool handled) override { ui::EventDispatchDetails details = InputMethodChromeOS::ProcessKeyEventPostIME( key_event, std::move(ack_callback), skip_process_filtered, handled); if (!skip_process_filtered) { - process_key_event_post_ime_args_.event = key_event; + process_key_event_post_ime_args_.event = *key_event; process_key_event_post_ime_args_.handled = handled; ++process_key_event_post_ime_call_count_; } @@ -110,72 +106,6 @@ class TestableInputMethodChromeOS : public InputMethodChromeOS { int process_key_event_post_ime_call_count_; }; -class SynchronousKeyEventHandler { - public: - SynchronousKeyEventHandler(uint32_t expected_keyval, - uint32_t expected_keycode, - uint32_t expected_state, - KeyEventHandlerBehavior behavior) - : expected_keyval_(expected_keyval), - expected_keycode_(expected_keycode), - expected_state_(expected_state), - behavior_(behavior) {} - - virtual ~SynchronousKeyEventHandler() {} - - void Run(uint32_t keyval, - uint32_t keycode, - uint32_t state, - const KeyEventCallback& callback) { - EXPECT_EQ(expected_keyval_, keyval); - EXPECT_EQ(expected_keycode_, keycode); - EXPECT_EQ(expected_state_, state); - callback.Run(behavior_ == KEYEVENT_CONSUME); - } - - private: - const uint32_t expected_keyval_; - const uint32_t expected_keycode_; - const uint32_t expected_state_; - const KeyEventHandlerBehavior behavior_; - - DISALLOW_COPY_AND_ASSIGN(SynchronousKeyEventHandler); -}; - -class AsynchronousKeyEventHandler { - public: - AsynchronousKeyEventHandler(uint32_t expected_keyval, - uint32_t expected_keycode, - uint32_t expected_state) - : expected_keyval_(expected_keyval), - expected_keycode_(expected_keycode), - expected_state_(expected_state) {} - - virtual ~AsynchronousKeyEventHandler() {} - - void Run(uint32_t keyval, - uint32_t keycode, - uint32_t state, - const KeyEventCallback& callback) { - EXPECT_EQ(expected_keyval_, keyval); - EXPECT_EQ(expected_keycode_, keycode); - EXPECT_EQ(expected_state_, state); - callback_ = callback; - } - - void RunCallback(KeyEventHandlerBehavior behavior) { - callback_.Run(behavior == KEYEVENT_CONSUME); - } - - private: - const uint32_t expected_keyval_; - const uint32_t expected_keycode_; - const uint32_t expected_state_; - KeyEventCallback callback_; - - DISALLOW_COPY_AND_ASSIGN(AsynchronousKeyEventHandler); -}; - class SetSurroundingTextVerifier { public: SetSurroundingTextVerifier(const std::string& expected_surrounding_text, @@ -605,7 +535,8 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_NoAttribute) { EXPECT_EQ(0UL, composition_text.ime_text_spans[0].start_offset); EXPECT_EQ(kSampleAsciiText.size(), composition_text.ime_text_spans[0].end_offset); - EXPECT_FALSE(composition_text.ime_text_spans[0].thick); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThin, + composition_text.ime_text_spans[0].thickness); } TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_SingleUnderline) { @@ -615,7 +546,7 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_SingleUnderline) { CompositionText composition_text; composition_text.text = kSampleText; ImeTextSpan underline(ImeTextSpan::Type::kComposition, 1UL, 4UL, - SK_ColorBLACK, false, SK_ColorTRANSPARENT); + ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT); composition_text.ime_text_spans.push_back(underline); CompositionText composition_text2; @@ -630,9 +561,11 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_SingleUnderline) { composition_text2.ime_text_spans[0].start_offset); EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_offset), composition_text2.ime_text_spans[0].end_offset); - // Single underline represents as black thin line. - EXPECT_EQ(SK_ColorBLACK, composition_text2.ime_text_spans[0].underline_color); - EXPECT_FALSE(composition_text2.ime_text_spans[0].thick); + // Single underline represents as thin line with text color. + EXPECT_EQ(SK_ColorTRANSPARENT, + composition_text2.ime_text_spans[0].underline_color); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThin, + composition_text2.ime_text_spans[0].thickness); EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), composition_text2.ime_text_spans[0].background_color); } @@ -644,7 +577,8 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_DoubleUnderline) { CompositionText composition_text; composition_text.text = kSampleText; ImeTextSpan underline(ImeTextSpan::Type::kComposition, 1UL, 4UL, - SK_ColorBLACK, true, SK_ColorTRANSPARENT); + ui::ImeTextSpan::Thickness::kThick, + SK_ColorTRANSPARENT); composition_text.ime_text_spans.push_back(underline); CompositionText composition_text2; @@ -659,9 +593,11 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_DoubleUnderline) { composition_text2.ime_text_spans[0].start_offset); EXPECT_EQ(GetOffsetInUTF16(kSampleText, underline.end_offset), composition_text2.ime_text_spans[0].end_offset); - // Double underline represents as black thick line. - EXPECT_EQ(SK_ColorBLACK, composition_text2.ime_text_spans[0].underline_color); - EXPECT_TRUE(composition_text2.ime_text_spans[0].thick); + // Double underline represents as thick line with text color. + EXPECT_EQ(SK_ColorTRANSPARENT, + composition_text2.ime_text_spans[0].underline_color); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThick, + composition_text2.ime_text_spans[0].thickness); EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), composition_text2.ime_text_spans[0].background_color); } @@ -672,8 +608,9 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_ErrorUnderline) { // Set up chromeos composition text with one underline attribute. CompositionText composition_text; composition_text.text = kSampleText; - ImeTextSpan underline(ImeTextSpan::Type::kComposition, 1UL, 4UL, SK_ColorRED, - false, SK_ColorTRANSPARENT); + ImeTextSpan underline(ImeTextSpan::Type::kComposition, 1UL, 4UL, + ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT); + underline.underline_color = SK_ColorRED; composition_text.ime_text_spans.push_back(underline); CompositionText composition_text2; @@ -689,7 +626,8 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_ErrorUnderline) { composition_text2.ime_text_spans[0].end_offset); // Error underline represents as red thin line. EXPECT_EQ(SK_ColorRED, composition_text2.ime_text_spans[0].underline_color); - EXPECT_FALSE(composition_text2.ime_text_spans[0].thick); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThin, + composition_text2.ime_text_spans[0].thickness); } TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_Selection) { @@ -712,8 +650,10 @@ TEST_F(InputMethodChromeOSTest, ExtractCompositionTextTest_Selection) { composition_text2.ime_text_spans[0].start_offset); EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), composition_text2.ime_text_spans[0].end_offset); - EXPECT_EQ(SK_ColorBLACK, composition_text2.ime_text_spans[0].underline_color); - EXPECT_TRUE(composition_text2.ime_text_spans[0].thick); + EXPECT_EQ(SK_ColorTRANSPARENT, + composition_text2.ime_text_spans[0].underline_color); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThick, + composition_text2.ime_text_spans[0].thickness); EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), composition_text2.ime_text_spans[0].background_color); } @@ -743,8 +683,10 @@ TEST_F(InputMethodChromeOSTest, composition_text2.ime_text_spans[0].start_offset); EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), composition_text2.ime_text_spans[0].end_offset); - EXPECT_EQ(SK_ColorBLACK, composition_text2.ime_text_spans[0].underline_color); - EXPECT_TRUE(composition_text2.ime_text_spans[0].thick); + EXPECT_EQ(SK_ColorTRANSPARENT, + composition_text2.ime_text_spans[0].underline_color); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThick, + composition_text2.ime_text_spans[0].thickness); EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), composition_text2.ime_text_spans[0].background_color); } @@ -774,8 +716,10 @@ TEST_F(InputMethodChromeOSTest, composition_text2.ime_text_spans[0].start_offset); EXPECT_EQ(GetOffsetInUTF16(kSampleText, composition_text.selection.end()), composition_text2.ime_text_spans[0].end_offset); - EXPECT_EQ(SK_ColorBLACK, composition_text2.ime_text_spans[0].underline_color); - EXPECT_TRUE(composition_text2.ime_text_spans[0].thick); + EXPECT_EQ(SK_ColorTRANSPARENT, + composition_text2.ime_text_spans[0].underline_color); + EXPECT_EQ(ui::ImeTextSpan::Thickness::kThick, + composition_text2.ime_text_spans[0].thickness); EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), composition_text2.ime_text_spans[0].background_color); } @@ -913,14 +857,14 @@ TEST_F(InputMethodChromeOSKeyEventTest, KeyEventDelayResponseTest) { EXPECT_EQ(0, inserted_char_); // Do callback. - mock_ime_engine_handler_->last_passed_callback().Run(true); + std::move(mock_ime_engine_handler_->last_passed_callback()).Run(true); // Check the results EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count()); - const ui::KeyEvent* stored_event = + const ui::KeyEvent stored_event = ime_->process_key_event_post_ime_args().event; - EXPECT_EQ(ui::VKEY_A, stored_event->key_code()); - EXPECT_EQ(kFlags, stored_event->flags()); + EXPECT_EQ(ui::VKEY_A, stored_event.key_code()); + EXPECT_EQ(kFlags, stored_event.flags()); EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled); EXPECT_EQ(L'A', inserted_char_); @@ -966,16 +910,15 @@ TEST_F(InputMethodChromeOSKeyEventTest, MultiKeyEventDelayResponseTest) { EXPECT_EQ(0, composition_text_.text[0]); // Do callback for first key event. - first_callback.Run(true); + std::move(first_callback).Run(true); EXPECT_EQ(comp.text, composition_text_.text); // Check the results for first key event. EXPECT_EQ(1, ime_->process_key_event_post_ime_call_count()); - const ui::KeyEvent* stored_event = - ime_->process_key_event_post_ime_args().event; - EXPECT_EQ(ui::VKEY_B, stored_event->key_code()); - EXPECT_EQ(kFlags, stored_event->flags()); + ui::KeyEvent stored_event = ime_->process_key_event_post_ime_args().event; + EXPECT_EQ(ui::VKEY_B, stored_event.key_code()); + EXPECT_EQ(kFlags, stored_event.flags()); EXPECT_TRUE(ime_->process_key_event_post_ime_args().handled); EXPECT_EQ(0, inserted_char_); @@ -985,8 +928,8 @@ TEST_F(InputMethodChromeOSKeyEventTest, MultiKeyEventDelayResponseTest) { // Check the results for second key event. EXPECT_EQ(2, ime_->process_key_event_post_ime_call_count()); stored_event = ime_->process_key_event_post_ime_args().event; - EXPECT_EQ(ui::VKEY_C, stored_event->key_code()); - EXPECT_EQ(kFlags, stored_event->flags()); + EXPECT_EQ(ui::VKEY_C, stored_event.key_code()); + EXPECT_EQ(kFlags, stored_event.flags()); EXPECT_FALSE(ime_->process_key_event_post_ime_args().handled); EXPECT_EQ(L'C', inserted_char_); @@ -1030,7 +973,8 @@ TEST_F(InputMethodChromeOSKeyEventTest, DeadKeyPressTest) { 0, DomKey::DeadKeyFromCombiningCharacter('^'), EventTimeForNow()); - ime_->ProcessKeyEventPostIME(&eventA, nullptr, false, true); + ime_->ProcessKeyEventPostIME(&eventA, InputMethodChromeOS::AckCallback(), + false, true); const ui::KeyEvent& key_event = dispatched_key_event_; diff --git a/chromium/ui/base/ime/input_method_factory.cc b/chromium/ui/base/ime/input_method_factory.cc index a1b1798fba6..d93dd27f82f 100644 --- a/chromium/ui/base/ime/input_method_factory.cc +++ b/chromium/ui/base/ime/input_method_factory.cc @@ -8,12 +8,14 @@ #include "base/memory/ptr_util.h" #include "build/build_config.h" #include "ui/base/ime/mock_input_method.h" +#include "ui/base/ui_base_features.h" #include "ui/gfx/switches.h" #if defined(OS_CHROMEOS) #include "ui/base/ime/input_method_chromeos.h" #elif defined(OS_WIN) #include "ui/base/ime/input_method_win.h" +#include "ui/base/ime/input_method_win_tsf.h" #elif defined(OS_MACOSX) #include "ui/base/ime/input_method_mac.h" #elif defined(USE_AURA) && defined(USE_X11) @@ -55,6 +57,8 @@ std::unique_ptr<InputMethod> CreateInputMethod( #if defined(OS_CHROMEOS) return std::make_unique<InputMethodChromeOS>(delegate); #elif defined(OS_WIN) + if (base::FeatureList::IsEnabled(features::kTSFImeSupport)) + return std::make_unique<InputMethodWinTSF>(delegate, widget); return std::make_unique<InputMethodWin>(delegate, widget); #elif defined(OS_MACOSX) return std::make_unique<InputMethodMac>(delegate); diff --git a/chromium/ui/base/ime/input_method_initializer.cc b/chromium/ui/base/ime/input_method_initializer.cc index 026b564b502..b64f362a5d8 100644 --- a/chromium/ui/base/ime/input_method_initializer.cc +++ b/chromium/ui/base/ime/input_method_initializer.cc @@ -11,6 +11,9 @@ #elif defined(USE_AURA) && defined(OS_LINUX) #include "base/logging.h" #include "ui/base/ime/linux/fake_input_method_context_factory.h" +#elif defined(OS_WIN) +#include "ui/base/ime/input_method_factory.h" +#include "ui/base/ime/win/tsf_bridge.h" #endif namespace { @@ -27,12 +30,16 @@ namespace ui { void InitializeInputMethod() { #if defined(OS_CHROMEOS) IMEBridge::Initialize(); +#elif defined(OS_WIN) + TSFBridge::Initialize(); #endif } void ShutdownInputMethod() { #if defined(OS_CHROMEOS) IMEBridge::Shutdown(); +#elif defined(OS_WIN) + TSFBridge::Shutdown(); #endif } @@ -50,6 +57,10 @@ void InitializeInputMethodForTesting() { << "else."; LinuxInputMethodContextFactory::SetInstance( g_linux_input_method_context_factory_for_testing); +#elif defined(OS_WIN) + // Make sure COM is initialized because TSF depends on COM. + CoInitialize(nullptr); + TSFBridge::Initialize(); #endif } @@ -64,6 +75,9 @@ void ShutdownInputMethodForTesting() { LinuxInputMethodContextFactory::SetInstance(NULL); delete g_linux_input_method_context_factory_for_testing; g_linux_input_method_context_factory_for_testing = NULL; +#elif defined(OS_WIN) + TSFBridge::Shutdown(); + CoUninitialize(); #endif } diff --git a/chromium/ui/base/ime/input_method_mac.h b/chromium/ui/base/ime/input_method_mac.h index 25d371881e0..7ca676d113a 100644 --- a/chromium/ui/base/ime/input_method_mac.h +++ b/chromium/ui/base/ime/input_method_mac.h @@ -20,8 +20,6 @@ class UI_BASE_IME_EXPORT InputMethodMac : public InputMethodBase { ~InputMethodMac() override; // Overriden from InputMethod. - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, - NativeEventResult* result) override; ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override; void OnCaretBoundsChanged(const TextInputClient* client) override; void CancelComposition(const TextInputClient* client) override; diff --git a/chromium/ui/base/ime/input_method_mac.mm b/chromium/ui/base/ime/input_method_mac.mm index d048773109c..1b4e953d7c0 100644 --- a/chromium/ui/base/ime/input_method_mac.mm +++ b/chromium/ui/base/ime/input_method_mac.mm @@ -15,11 +15,6 @@ InputMethodMac::InputMethodMac(internal::InputMethodDelegate* delegate) { InputMethodMac::~InputMethodMac() { } -bool InputMethodMac::OnUntranslatedIMEMessage(const base::NativeEvent& event, - NativeEventResult* result) { - return false; -} - ui::EventDispatchDetails InputMethodMac::DispatchKeyEvent(ui::KeyEvent* event) { // This is used on Mac only to dispatch events post-IME. return DispatchKeyEventPostIME(event); diff --git a/chromium/ui/base/ime/input_method_minimal.cc b/chromium/ui/base/ime/input_method_minimal.cc index d040d9fd415..331fc8e9cf9 100644 --- a/chromium/ui/base/ime/input_method_minimal.cc +++ b/chromium/ui/base/ime/input_method_minimal.cc @@ -19,12 +19,6 @@ InputMethodMinimal::InputMethodMinimal( InputMethodMinimal::~InputMethodMinimal() {} -bool InputMethodMinimal::OnUntranslatedIMEMessage( - const base::NativeEvent& event, - NativeEventResult* result) { - return false; -} - ui::EventDispatchDetails InputMethodMinimal::DispatchKeyEvent( ui::KeyEvent* event) { DCHECK(event->type() == ET_KEY_PRESSED || event->type() == ET_KEY_RELEASED); diff --git a/chromium/ui/base/ime/input_method_minimal.h b/chromium/ui/base/ime/input_method_minimal.h index 96359226adc..259c9993529 100644 --- a/chromium/ui/base/ime/input_method_minimal.h +++ b/chromium/ui/base/ime/input_method_minimal.h @@ -18,8 +18,6 @@ class UI_BASE_IME_EXPORT InputMethodMinimal : public InputMethodBase { ~InputMethodMinimal() override; // Overriden from InputMethod. - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, - NativeEventResult* result) override; ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override; void OnCaretBoundsChanged(const TextInputClient* client) override; void CancelComposition(const TextInputClient* client) override; diff --git a/chromium/ui/base/ime/input_method_win.cc b/chromium/ui/base/ime/input_method_win.cc index 06f8831222d..4c3b458047e 100644 --- a/chromium/ui/base/ime/input_method_win.cc +++ b/chromium/ui/base/ime/input_method_win.cc @@ -25,10 +25,6 @@ namespace ui { namespace { -// Extra number of chars before and after selection (or composition) range which -// is returned to IME for improving conversion accuracy. -static const size_t kExtraNumberOfChars = 20; - ui::EventDispatchDetails DispatcherDestroyedDetails() { ui::EventDispatchDetails dispatcher_details; dispatcher_details.dispatcher_destroyed = true; @@ -39,14 +35,12 @@ ui::EventDispatchDetails DispatcherDestroyedDetails() { InputMethodWin::InputMethodWin(internal::InputMethodDelegate* delegate, HWND toplevel_window_handle) - : toplevel_window_handle_(toplevel_window_handle), + : InputMethodWinBase(delegate, toplevel_window_handle), pending_requested_direction_(base::i18n::UNKNOWN_DIRECTION), - accept_carriage_return_(false), enabled_(false), is_candidate_popup_open_(false), composing_window_handle_(NULL), weak_ptr_factory_(this) { - SetDelegate(delegate); imm32_manager_.SetInputLanguage(); } @@ -58,7 +52,7 @@ void InputMethodWin::OnFocus() { } bool InputMethodWin::OnUntranslatedIMEMessage( - const base::NativeEvent& event, + const MSG event, InputMethod::NativeEventResult* result) { LRESULT original_result = 0; BOOL handled = FALSE; @@ -106,7 +100,7 @@ ui::EventDispatchDetails InputMethodWin::DispatchKeyEvent(ui::KeyEvent* event) { if (!event->HasNativeEvent()) return DispatchFabricatedKeyEvent(event); - const base::NativeEvent& native_key_event = event->native_event(); + const PlatformEvent& native_key_event = event->native_event(); BOOL handled = FALSE; if (native_key_event.message == WM_CHAR) { auto ref = weak_ptr_factory_.GetWeakPtr(); @@ -178,11 +172,12 @@ ui::EventDispatchDetails InputMethodWin::DispatchKeyEvent(ui::KeyEvent* event) { // messages have been combined in the event processing flow. if (char_msgs.size() <= 1 && GetEngine() && GetEngine()->IsInterestedInKeyEvent()) { - ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = base::Bind( - &InputMethodWin::ProcessKeyEventDone, weak_ptr_factory_.GetWeakPtr(), - base::Owned(new ui::KeyEvent(*event)), - base::Owned(new std::vector<MSG>(char_msgs))); - GetEngine()->ProcessKeyEvent(*event, callback); + ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback = + base::BindOnce(&InputMethodWin::ProcessKeyEventDone, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(new ui::KeyEvent(*event)), + base::Owned(new std::vector<MSG>(char_msgs))); + GetEngine()->ProcessKeyEvent(*event, std::move(callback)); return ui::EventDispatchDetails(); } @@ -313,44 +308,7 @@ void InputMethodWin::OnDidChangeFocusedClient( // bounds has not changed. OnCaretBoundsChanged(focused); } - if (focused_before != focused) - accept_carriage_return_ = false; -} - -LRESULT InputMethodWin::OnChar(HWND window_handle, - UINT message, - WPARAM wparam, - LPARAM lparam, - const base::NativeEvent& event, - BOOL* handled) { - *handled = TRUE; - - // We need to send character events to the focused text input client event if - // its text input type is ui::TEXT_INPUT_TYPE_NONE. - if (GetTextInputClient()) { - const base::char16 kCarriageReturn = L'\r'; - const base::char16 ch = static_cast<base::char16>(wparam); - // A mask to determine the previous key state from |lparam|. The value is 1 - // if the key is down before the message is sent, or it is 0 if the key is - // up. - const uint32_t kPrevKeyDownBit = 0x40000000; - if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit)) - accept_carriage_return_ = true; - // Conditionally ignore '\r' events to work around crbug.com/319100. - // TODO(yukawa, IME): Figure out long-term solution. - if (ch != kCarriageReturn || accept_carriage_return_) { - ui::KeyEvent char_event(event); - GetTextInputClient()->InsertChar(char_event); - } - } - - // Explicitly show the system menu at a good location on [Alt]+[Space]. - // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system - // menu causes undesirable titlebar artifacts in the classic theme. - if (message == WM_SYSCHAR && wparam == VK_SPACE) - gfx::ShowSystemMenu(window_handle); - - return 0; + InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused); } LRESULT InputMethodWin::OnImeSetContext(HWND window_handle, @@ -481,188 +439,6 @@ LRESULT InputMethodWin::OnImeNotify(UINT message, return 0; } -LRESULT InputMethodWin::OnImeRequest(UINT message, - WPARAM wparam, - LPARAM lparam, - BOOL* handled) { - *handled = FALSE; - - // Should not receive WM_IME_REQUEST message, if IME is disabled. - const ui::TextInputType type = GetTextInputType(); - if (type == ui::TEXT_INPUT_TYPE_NONE || - type == ui::TEXT_INPUT_TYPE_PASSWORD) { - return 0; - } - - switch (wparam) { - case IMR_RECONVERTSTRING: - *handled = TRUE; - return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); - case IMR_DOCUMENTFEED: - *handled = TRUE; - return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); - case IMR_QUERYCHARPOSITION: - *handled = TRUE; - return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam)); - default: - return 0; - } -} - -LRESULT InputMethodWin::OnDocumentFeed(RECONVERTSTRING* reconv) { - ui::TextInputClient* client = GetTextInputClient(); - if (!client) - return 0; - - gfx::Range text_range; - if (!client->GetTextRange(&text_range) || text_range.is_empty()) - return 0; - - bool result = false; - gfx::Range target_range; - if (client->HasCompositionText()) - result = client->GetCompositionTextRange(&target_range); - - if (!result || target_range.is_empty()) { - if (!client->GetSelectionRange(&target_range) || - !target_range.IsValid()) { - return 0; - } - } - - if (!text_range.Contains(target_range)) - return 0; - - if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars) - text_range.set_start(target_range.GetMin() - kExtraNumberOfChars); - - if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars) - text_range.set_end(target_range.GetMax() + kExtraNumberOfChars); - - size_t len = text_range.length(); - size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); - - if (!reconv) - return need_size; - - if (reconv->dwSize < need_size) - return 0; - - base::string16 text; - if (!GetTextInputClient()->GetTextFromRange(text_range, &text)) - return 0; - DCHECK_EQ(text_range.length(), text.length()); - - reconv->dwVersion = 0; - reconv->dwStrLen = len; - reconv->dwStrOffset = sizeof(RECONVERTSTRING); - reconv->dwCompStrLen = - client->HasCompositionText() ? target_range.length() : 0; - reconv->dwCompStrOffset = - (target_range.GetMin() - text_range.start()) * sizeof(WCHAR); - reconv->dwTargetStrLen = target_range.length(); - reconv->dwTargetStrOffset = reconv->dwCompStrOffset; - - memcpy((char*)reconv + sizeof(RECONVERTSTRING), - text.c_str(), len * sizeof(WCHAR)); - - // According to Microsoft API document, IMR_RECONVERTSTRING and - // IMR_DOCUMENTFEED should return reconv, but some applications return - // need_size. - return reinterpret_cast<LRESULT>(reconv); -} - -LRESULT InputMethodWin::OnReconvertString(RECONVERTSTRING* reconv) { - ui::TextInputClient* client = GetTextInputClient(); - if (!client) - return 0; - - // If there is a composition string already, we don't allow reconversion. - if (client->HasCompositionText()) - return 0; - - gfx::Range text_range; - if (!client->GetTextRange(&text_range) || text_range.is_empty()) - return 0; - - gfx::Range selection_range; - if (!client->GetSelectionRange(&selection_range) || - selection_range.is_empty()) { - return 0; - } - - DCHECK(text_range.Contains(selection_range)); - - size_t len = selection_range.length(); - size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); - - if (!reconv) - return need_size; - - if (reconv->dwSize < need_size) - return 0; - - // TODO(penghuang): Return some extra context to help improve IME's - // reconversion accuracy. - base::string16 text; - if (!GetTextInputClient()->GetTextFromRange(selection_range, &text)) - return 0; - DCHECK_EQ(selection_range.length(), text.length()); - - reconv->dwVersion = 0; - reconv->dwStrLen = len; - reconv->dwStrOffset = sizeof(RECONVERTSTRING); - reconv->dwCompStrLen = len; - reconv->dwCompStrOffset = 0; - reconv->dwTargetStrLen = len; - reconv->dwTargetStrOffset = 0; - - memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), - text.c_str(), len * sizeof(WCHAR)); - - // According to Microsoft API document, IMR_RECONVERTSTRING and - // IMR_DOCUMENTFEED should return reconv, but some applications return - // need_size. - return reinterpret_cast<LRESULT>(reconv); -} - -LRESULT InputMethodWin::OnQueryCharPosition(IMECHARPOSITION* char_positon) { - if (!char_positon) - return 0; - - if (char_positon->dwSize < sizeof(IMECHARPOSITION)) - return 0; - - ui::TextInputClient* client = GetTextInputClient(); - if (!client) - return 0; - - // Tentatively assume that the returned value from |client| is DIP (Density - // Independent Pixel). See the comment in text_input_client.h and - // http://crbug.com/360334. - gfx::Rect dip_rect; - if (client->HasCompositionText()) { - if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos, - &dip_rect)) { - return 0; - } - } else { - // If there is no composition and the first character is queried, returns - // the caret bounds. This behavior is the same to that of RichEdit control. - if (char_positon->dwCharPos != 0) - return 0; - dip_rect = client->GetCaretBounds(); - } - const gfx::Rect rect = - display::win::ScreenWin::DIPToScreenRect(toplevel_window_handle_, - dip_rect); - - char_positon->pt.x = rect.x(); - char_positon->pt.y = rect.y(); - char_positon->cLineHeight = rect.height(); - return 1; // returns non-zero value when succeeded. -} - void InputMethodWin::RefreshInputLanguage() { TextInputType type_original = GetTextInputType(); imm32_manager_.SetInputLanguage(); @@ -677,18 +453,6 @@ void InputMethodWin::RefreshInputLanguage() { } } -bool InputMethodWin::IsWindowFocused(const TextInputClient* client) const { - if (!client) - return false; - // When Aura is enabled, |attached_window_handle| should always be a top-level - // window. So we can safely assume that |attached_window_handle| is ready for - // receiving keyboard input as long as it is an active window. This works well - // even when the |attached_window_handle| becomes active but has not received - // WM_FOCUS yet. - return toplevel_window_handle_ && - GetActiveWindow() == toplevel_window_handle_; -} - ui::EventDispatchDetails InputMethodWin::DispatchFabricatedKeyEvent( ui::KeyEvent* event) { // The key event if from calling input.ime.sendKeyEvent or test. diff --git a/chromium/ui/base/ime/input_method_win.h b/chromium/ui/base/ime/input_method_win.h index c41504e29ff..603c456fb16 100644 --- a/chromium/ui/base/ime/input_method_win.h +++ b/chromium/ui/base/ime/input_method_win.h @@ -11,13 +11,13 @@ #include "base/compiler_specific.h" #include "base/macros.h" -#include "ui/base/ime/input_method_base.h" +#include "ui/base/ime/input_method_win_base.h" #include "ui/base/ime/win/imm32_manager.h" namespace ui { // A common InputMethod implementation based on IMM32. -class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase { +class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodWinBase { public: InputMethodWin(internal::InputMethodDelegate* delegate, HWND toplevel_window_handle); @@ -27,7 +27,7 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase { void OnFocus() override; // Overridden from InputMethod: - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, + bool OnUntranslatedIMEMessage(const MSG event, NativeEventResult* result) override; ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override; void OnTextInputTypeChanged(const TextInputClient* client) override; @@ -47,14 +47,6 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase { TextInputClient* focused) override; private: - // For both WM_CHAR and WM_SYSCHAR - LRESULT OnChar(HWND window_handle, - UINT message, - WPARAM wparam, - LPARAM lparam, - const base::NativeEvent& event, - BOOL* handled); - LRESULT OnImeSetContext(HWND window_handle, UINT message, WPARAM wparam, @@ -80,23 +72,8 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase { LPARAM lparam, BOOL* handled); - // Some IMEs rely on WM_IME_REQUEST message even when TSF is enabled. So - // OnImeRequest (and its actual implementations as OnDocumentFeed, - // OnReconvertString, and OnQueryCharPosition) are placed in this base class. - LRESULT OnImeRequest(UINT message, - WPARAM wparam, - LPARAM lparam, - BOOL* handled); - LRESULT OnDocumentFeed(RECONVERTSTRING* reconv); - LRESULT OnReconvertString(RECONVERTSTRING* reconv); - LRESULT OnQueryCharPosition(IMECHARPOSITION* char_positon); - void RefreshInputLanguage(); - // Returns true if the Win32 native window bound to |client| is considered - // to be ready for receiving keyboard input. - bool IsWindowFocused(const TextInputClient* client) const; - ui::EventDispatchDetails DispatchFabricatedKeyEvent(ui::KeyEvent* event); // Asks the client to confirm current composition text. @@ -118,21 +95,11 @@ class UI_BASE_IME_EXPORT InputMethodWin : public InputMethodBase { // (See "ui/base/ime/win/ime_input.h" for its details.) ui::IMM32Manager imm32_manager_; - // The toplevel window handle. - // On non-Aura environment, this value is not used and always NULL. - const HWND toplevel_window_handle_; - // The new text direction and layout alignment requested by the user by // pressing ctrl-shift. It'll be sent to the text input client when the key // is released. base::i18n::TextDirection pending_requested_direction_; - // Represents if WM_CHAR[wparam=='\r'] should be dispatched to the focused - // text input client or ignored silently. This flag is introduced as a quick - // workaround against crbug.com/319100 - // TODO(yukawa, IME): Figure out long-term solution. - bool accept_carriage_return_; - // True when an IME should be allowed to process key events. bool enabled_; diff --git a/chromium/ui/base/ime/input_method_win_base.cc b/chromium/ui/base/ime/input_method_win_base.cc new file mode 100644 index 00000000000..0e89451afb3 --- /dev/null +++ b/chromium/ui/base/ime/input_method_win_base.cc @@ -0,0 +1,276 @@ +// Copyright 2018 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_win_base.h" + +#include <stddef.h> +#include <stdint.h> +#include <cwctype> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "ui/base/ime/ime_bridge.h" +#include "ui/base/ime/ime_engine_handler_interface.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/base/ime/win/tsf_input_scope.h" +#include "ui/display/win/screen_win.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/event_utils.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/win/hwnd_util.h" + +namespace ui { +namespace { + +// Extra number of chars before and after selection (or composition) range which +// is returned to IME for improving conversion accuracy. +constexpr size_t kExtraNumberOfChars = 20; + +} // namespace + +InputMethodWinBase::InputMethodWinBase(internal::InputMethodDelegate* delegate, + HWND toplevel_window_handle) + : InputMethodBase(delegate), + toplevel_window_handle_(toplevel_window_handle) {} + +InputMethodWinBase::~InputMethodWinBase() {} + +void InputMethodWinBase::OnDidChangeFocusedClient( + TextInputClient* focused_before, + TextInputClient* focused) { + if (focused_before != focused) + accept_carriage_return_ = false; +} + +bool InputMethodWinBase::IsWindowFocused(const TextInputClient* client) const { + if (!client) + return false; + // When Aura is enabled, |attached_window_handle| should always be a top-level + // window. So we can safely assume that |attached_window_handle| is ready for + // receiving keyboard input as long as it is an active window. This works well + // even when the |attached_window_handle| becomes active but has not received + // WM_FOCUS yet. + return toplevel_window_handle_ && + GetActiveWindow() == toplevel_window_handle_; +} + +LRESULT InputMethodWinBase::OnChar(HWND window_handle, + UINT message, + WPARAM wparam, + LPARAM lparam, + const PlatformEvent& event, + BOOL* handled) { + *handled = TRUE; + + // We need to send character events to the focused text input client event if + // its text input type is ui::TEXT_INPUT_TYPE_NONE. + if (GetTextInputClient()) { + const base::char16 kCarriageReturn = L'\r'; + const base::char16 ch = static_cast<base::char16>(wparam); + // A mask to determine the previous key state from |lparam|. The value is 1 + // if the key is down before the message is sent, or it is 0 if the key is + // up. + const uint32_t kPrevKeyDownBit = 0x40000000; + if (ch == kCarriageReturn && !(lparam & kPrevKeyDownBit)) + accept_carriage_return_ = true; + // Conditionally ignore '\r' events to work around https://crbug.com/319100. + // TODO(yukawa, IME): Figure out long-term solution. + if (ch != kCarriageReturn || accept_carriage_return_) { + ui::KeyEvent char_event(event); + GetTextInputClient()->InsertChar(char_event); + } + } + + // Explicitly show the system menu at a good location on [Alt]+[Space]. + // Note: Setting |handled| to FALSE for DefWindowProc triggering of the system + // menu causes undesirable titlebar artifacts in the classic theme. + if (message == WM_SYSCHAR && wparam == VK_SPACE) + gfx::ShowSystemMenu(window_handle); + + return 0; +} + +LRESULT InputMethodWinBase::OnImeRequest(UINT message, + WPARAM wparam, + LPARAM lparam, + BOOL* handled) { + *handled = FALSE; + + // Should not receive WM_IME_REQUEST message, if IME is disabled. + const ui::TextInputType type = GetTextInputType(); + if (type == ui::TEXT_INPUT_TYPE_NONE || + type == ui::TEXT_INPUT_TYPE_PASSWORD) { + return 0; + } + + switch (wparam) { + case IMR_RECONVERTSTRING: + *handled = TRUE; + return OnReconvertString(reinterpret_cast<RECONVERTSTRING*>(lparam)); + case IMR_DOCUMENTFEED: + *handled = TRUE; + return OnDocumentFeed(reinterpret_cast<RECONVERTSTRING*>(lparam)); + case IMR_QUERYCHARPOSITION: + *handled = TRUE; + return OnQueryCharPosition(reinterpret_cast<IMECHARPOSITION*>(lparam)); + default: + return 0; + } +} + +LRESULT InputMethodWinBase::OnDocumentFeed(RECONVERTSTRING* reconv) { + ui::TextInputClient* client = GetTextInputClient(); + if (!client) + return 0; + + gfx::Range text_range; + if (!client->GetTextRange(&text_range) || text_range.is_empty()) + return 0; + + bool result = false; + gfx::Range target_range; + if (client->HasCompositionText()) + result = client->GetCompositionTextRange(&target_range); + + if (!result || target_range.is_empty()) { + if (!client->GetSelectionRange(&target_range) || !target_range.IsValid()) { + return 0; + } + } + + if (!text_range.Contains(target_range)) + return 0; + + if (target_range.GetMin() - text_range.start() > kExtraNumberOfChars) + text_range.set_start(target_range.GetMin() - kExtraNumberOfChars); + + if (text_range.end() - target_range.GetMax() > kExtraNumberOfChars) + text_range.set_end(target_range.GetMax() + kExtraNumberOfChars); + + size_t len = text_range.length(); + size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); + + if (!reconv) + return need_size; + + if (reconv->dwSize < need_size) + return 0; + + base::string16 text; + if (!GetTextInputClient()->GetTextFromRange(text_range, &text)) + return 0; + DCHECK_EQ(text_range.length(), text.length()); + + reconv->dwVersion = 0; + reconv->dwStrLen = len; + reconv->dwStrOffset = sizeof(RECONVERTSTRING); + reconv->dwCompStrLen = + client->HasCompositionText() ? target_range.length() : 0; + reconv->dwCompStrOffset = + (target_range.GetMin() - text_range.start()) * sizeof(WCHAR); + reconv->dwTargetStrLen = target_range.length(); + reconv->dwTargetStrOffset = reconv->dwCompStrOffset; + + memcpy((char*)reconv + sizeof(RECONVERTSTRING), text.c_str(), + len * sizeof(WCHAR)); + + // According to Microsoft API document, IMR_RECONVERTSTRING and + // IMR_DOCUMENTFEED should return reconv, but some applications return + // need_size. + return reinterpret_cast<LRESULT>(reconv); +} + +LRESULT InputMethodWinBase::OnReconvertString(RECONVERTSTRING* reconv) { + ui::TextInputClient* client = GetTextInputClient(); + if (!client) + return 0; + + // If there is a composition string already, we don't allow reconversion. + if (client->HasCompositionText()) + return 0; + + gfx::Range text_range; + if (!client->GetTextRange(&text_range) || text_range.is_empty()) + return 0; + + gfx::Range selection_range; + if (!client->GetSelectionRange(&selection_range) || + selection_range.is_empty()) { + return 0; + } + + DCHECK(text_range.Contains(selection_range)); + + size_t len = selection_range.length(); + size_t need_size = sizeof(RECONVERTSTRING) + len * sizeof(WCHAR); + + if (!reconv) + return need_size; + + if (reconv->dwSize < need_size) + return 0; + + // TODO(penghuang): Return some extra context to help improve IME's + // reconversion accuracy. + base::string16 text; + if (!GetTextInputClient()->GetTextFromRange(selection_range, &text)) + return 0; + DCHECK_EQ(selection_range.length(), text.length()); + + reconv->dwVersion = 0; + reconv->dwStrLen = len; + reconv->dwStrOffset = sizeof(RECONVERTSTRING); + reconv->dwCompStrLen = len; + reconv->dwCompStrOffset = 0; + reconv->dwTargetStrLen = len; + reconv->dwTargetStrOffset = 0; + + memcpy(reinterpret_cast<char*>(reconv) + sizeof(RECONVERTSTRING), + text.c_str(), len * sizeof(WCHAR)); + + // According to Microsoft API document, IMR_RECONVERTSTRING and + // IMR_DOCUMENTFEED should return reconv, but some applications return + // need_size. + return reinterpret_cast<LRESULT>(reconv); +} + +LRESULT InputMethodWinBase::OnQueryCharPosition(IMECHARPOSITION* char_positon) { + if (!char_positon) + return 0; + + if (char_positon->dwSize < sizeof(IMECHARPOSITION)) + return 0; + + ui::TextInputClient* client = GetTextInputClient(); + if (!client) + return 0; + + // Tentatively assume that the returned value from |client| is DIP (Density + // Independent Pixel). See the comment in text_input_client.h and + // http://crbug.com/360334. + gfx::Rect dip_rect; + if (client->HasCompositionText()) { + if (!client->GetCompositionCharacterBounds(char_positon->dwCharPos, + &dip_rect)) { + return 0; + } + } else { + // If there is no composition and the first character is queried, returns + // the caret bounds. This behavior is the same to that of RichEdit control. + if (char_positon->dwCharPos != 0) + return 0; + dip_rect = client->GetCaretBounds(); + } + const gfx::Rect rect = display::win::ScreenWin::DIPToScreenRect( + toplevel_window_handle_, dip_rect); + + char_positon->pt.x = rect.x(); + char_positon->pt.y = rect.y(); + char_positon->cLineHeight = rect.height(); + return 1; // returns non-zero value when succeeded. +} + +} // namespace ui diff --git a/chromium/ui/base/ime/input_method_win_base.h b/chromium/ui/base/ime/input_method_win_base.h new file mode 100644 index 00000000000..9e63213046e --- /dev/null +++ b/chromium/ui/base/ime/input_method_win_base.h @@ -0,0 +1,67 @@ +// Copyright 2018 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_INPUT_METHOD_WIN_BASE_H_ +#define UI_BASE_IME_INPUT_METHOD_WIN_BASE_H_ + +#include <windows.h> + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "ui/base/ime/input_method_base.h" +#include "ui/base/ime/win/imm32_manager.h" + +namespace ui { + +// A common InputMethod base implementation for Windows. +class UI_BASE_IME_EXPORT InputMethodWinBase : public InputMethodBase { + public: + InputMethodWinBase(internal::InputMethodDelegate* delegate, + HWND toplevel_window_handle); + ~InputMethodWinBase() override; + + protected: + void OnDidChangeFocusedClient(TextInputClient* focused_before, + TextInputClient* focused) override; + + // Returns true if the Win32 native window bound to |client| is considered + // to be ready for receiving keyboard input. + bool IsWindowFocused(const TextInputClient* client) const; + + // For both WM_CHAR and WM_SYSCHAR + LRESULT OnChar(HWND window_handle, + UINT message, + WPARAM wparam, + LPARAM lparam, + const PlatformEvent& event, + BOOL* handled); + + // Some IMEs rely on WM_IME_REQUEST message even when TSF is enabled. So + // OnImeRequest (and its actual implementations as OnDocumentFeed, + // OnReconvertString, and OnQueryCharPosition) are placed in this base class. + LRESULT OnImeRequest(UINT message, + WPARAM wparam, + LPARAM lparam, + BOOL* handled); + LRESULT OnDocumentFeed(RECONVERTSTRING* reconv); + LRESULT OnReconvertString(RECONVERTSTRING* reconv); + LRESULT OnQueryCharPosition(IMECHARPOSITION* char_positon); + + // The toplevel window handle. + const HWND toplevel_window_handle_; + + // Represents if WM_CHAR[wparam=='\r'] should be dispatched to the focused + // text input client or ignored silently. This flag is introduced as a quick + // workaround against https://crbug.com/319100 + // TODO(yukawa, IME): Figure out long-term solution. + bool accept_carriage_return_ = false; + + DISALLOW_COPY_AND_ASSIGN(InputMethodWinBase); +}; + +} // namespace ui + +#endif // UI_BASE_IME_INPUT_METHOD_WIN_BASE_H_ diff --git a/chromium/ui/base/ime/input_method_win_tsf.cc b/chromium/ui/base/ime/input_method_win_tsf.cc new file mode 100644 index 00000000000..0cf6f88e39f --- /dev/null +++ b/chromium/ui/base/ime/input_method_win_tsf.cc @@ -0,0 +1,147 @@ +// Copyright 2018 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_win_tsf.h" + +#include "ui/base/ime/text_input_client.h" +#include "ui/base/ime/win/tsf_bridge.h" +#include "ui/base/ime/win/tsf_event_router.h" + +namespace ui { + +class InputMethodWinTSF::TSFEventObserver : public TSFEventRouterObserver { + public: + TSFEventObserver() = default; + + // Returns true if we know for sure that a candidate window (or IME suggest, + // etc.) is open. + bool IsCandidatePopupOpen() const { return is_candidate_popup_open_; } + + // Overridden from TSFEventRouterObserver: + void OnCandidateWindowCountChanged(size_t window_count) override { + is_candidate_popup_open_ = (window_count != 0); + } + + private: + // True if we know for sure that a candidate window is open. + bool is_candidate_popup_open_ = false; + + DISALLOW_COPY_AND_ASSIGN(TSFEventObserver); +}; + +InputMethodWinTSF::InputMethodWinTSF(internal::InputMethodDelegate* delegate, + HWND toplevel_window_handle) + : InputMethodWinBase(delegate, toplevel_window_handle), + tsf_event_observer_(new TSFEventObserver()), + tsf_event_router_(new TSFEventRouter(tsf_event_observer_.get())) {} + +InputMethodWinTSF::~InputMethodWinTSF() {} + +ui::EventDispatchDetails InputMethodWinTSF::DispatchKeyEvent( + ui::KeyEvent* event) { + // TODO(dtapuska): Handle WM_CHAR events. + return ui::EventDispatchDetails(); +} + +void InputMethodWinTSF::OnFocus() { + tsf_event_router_->SetManager( + ui::TSFBridge::GetInstance()->GetThreadManager().Get()); +} + +void InputMethodWinTSF::OnBlur() { + tsf_event_router_->SetManager(nullptr); +} + +bool InputMethodWinTSF::OnUntranslatedIMEMessage( + const MSG event, + InputMethod::NativeEventResult* result) { + LRESULT original_result = 0; + BOOL handled = FALSE; + // Even when TSF is enabled, following IMM32/Win32 messages must be handled. + switch (event.message) { + case WM_IME_REQUEST: + // Some TSF-native TIPs (Text Input Processors) such as ATOK and Mozc + // still rely on WM_IME_REQUEST message to implement reverse conversion. + original_result = + OnImeRequest(event.message, event.wParam, event.lParam, &handled); + break; + case WM_CHAR: + case WM_SYSCHAR: + // ui::InputMethod interface is responsible for handling Win32 character + // messages. For instance, we will be here in the following cases. + // - TIP is not activated. (e.g, the current language profile is English) + // - TIP does not handle and WM_KEYDOWN and WM_KEYDOWN is translated into + // WM_CHAR by TranslateMessage API. (e.g, TIP is turned off) + // - Another application sends WM_CHAR through SendMessage API. + original_result = OnChar(event.hwnd, event.message, event.wParam, + event.lParam, event, &handled); + break; + } + + if (result) + *result = original_result; + return !!handled; +} + +void InputMethodWinTSF::OnTextInputTypeChanged(const TextInputClient* client) { + if (!IsTextInputClientFocused(client) || !IsWindowFocused(client)) + return; + ui::TSFBridge::GetInstance()->CancelComposition(); + ui::TSFBridge::GetInstance()->OnTextInputTypeChanged(client); +} + +void InputMethodWinTSF::OnCaretBoundsChanged(const TextInputClient* client) { + if (!IsTextInputClientFocused(client) || !IsWindowFocused(client)) + return; + ui::TSFBridge::GetInstance()->OnTextLayoutChanged(); +} + +void InputMethodWinTSF::CancelComposition(const TextInputClient* client) { + if (IsTextInputClientFocused(client) && IsWindowFocused(client)) + ui::TSFBridge::GetInstance()->CancelComposition(); +} + +void InputMethodWinTSF::DetachTextInputClient(TextInputClient* client) { + InputMethodWinBase::DetachTextInputClient(client); + ui::TSFBridge::GetInstance()->RemoveFocusedClient(client); +} + +bool InputMethodWinTSF::IsCandidatePopupOpen() const { + return tsf_event_observer_->IsCandidatePopupOpen(); +} + +void InputMethodWinTSF::OnWillChangeFocusedClient( + TextInputClient* focused_before, + TextInputClient* focused) { + if (IsWindowFocused(focused_before)) { + ConfirmCompositionText(); + ui::TSFBridge::GetInstance()->RemoveFocusedClient(focused_before); + } +} + +void InputMethodWinTSF::OnDidChangeFocusedClient( + TextInputClient* focused_before, + TextInputClient* focused) { + if (IsWindowFocused(focused) && IsTextInputClientFocused(focused)) { + ui::TSFBridge::GetInstance()->SetFocusedClient(toplevel_window_handle_, + focused); + + // Force to update the input type since client's TextInputStateChanged() + // function might not be called if text input types before the client loses + // focus and after it acquires focus again are the same. + OnTextInputTypeChanged(focused); + + // Force to update caret bounds, in case the client thinks that the caret + // bounds has not changed. + OnCaretBoundsChanged(focused); + } + InputMethodWinBase::OnDidChangeFocusedClient(focused_before, focused); +} + +void InputMethodWinTSF::ConfirmCompositionText() { + if (!IsTextInputTypeNone()) + ui::TSFBridge::GetInstance()->ConfirmComposition(); +} + +} // namespace ui diff --git a/chromium/ui/base/ime/input_method_win_tsf.h b/chromium/ui/base/ime/input_method_win_tsf.h new file mode 100644 index 00000000000..4d8a7c465bd --- /dev/null +++ b/chromium/ui/base/ime/input_method_win_tsf.h @@ -0,0 +1,58 @@ +// Copyright 2018 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_INPUT_METHOD_WIN_TSF_H_ +#define UI_BASE_IME_INPUT_METHOD_WIN_TSF_H_ + +#include <windows.h> + +#include <string> + +#include "ui/base/ime/input_method_win_base.h" + +namespace ui { + +class TSFEventRouter; + +// An InputMethod implementation based on Windows TSF API. +class UI_BASE_IME_EXPORT InputMethodWinTSF : public InputMethodWinBase { + public: + InputMethodWinTSF(internal::InputMethodDelegate* delegate, + HWND toplevel_window_handle); + ~InputMethodWinTSF() override; + + // Overridden from InputMethod: + ui::EventDispatchDetails DispatchKeyEvent(ui::KeyEvent* event) override; + void OnFocus() override; + void OnBlur() override; + bool OnUntranslatedIMEMessage(const MSG event, + NativeEventResult* result) override; + void OnTextInputTypeChanged(const TextInputClient* client) override; + void OnCaretBoundsChanged(const TextInputClient* client) override; + void CancelComposition(const TextInputClient* client) override; + void DetachTextInputClient(TextInputClient* client) override; + bool IsCandidatePopupOpen() const override; + + // Overridden from InputMethodBase: + void OnWillChangeFocusedClient(TextInputClient* focused_before, + TextInputClient* focused) override; + void OnDidChangeFocusedClient(TextInputClient* focused_before, + TextInputClient* focused) override; + + private: + class TSFEventObserver; + + // Asks the client to confirm current composition text. + void ConfirmCompositionText(); + + // TSF event router and observer. + std::unique_ptr<TSFEventObserver> tsf_event_observer_; + std::unique_ptr<TSFEventRouter> tsf_event_router_; + + DISALLOW_COPY_AND_ASSIGN(InputMethodWinTSF); +}; + +} // namespace ui + +#endif // UI_BASE_IME_INPUT_METHOD_WIN_TSF_H_ diff --git a/chromium/ui/base/ime/linux/fake_input_method_context_factory.cc b/chromium/ui/base/ime/linux/fake_input_method_context_factory.cc index ec651ce0c04..f8d3102ee48 100644 --- a/chromium/ui/base/ime/linux/fake_input_method_context_factory.cc +++ b/chromium/ui/base/ime/linux/fake_input_method_context_factory.cc @@ -4,7 +4,6 @@ #include "ui/base/ime/linux/fake_input_method_context_factory.h" -#include "base/memory/ptr_util.h" #include "ui/base/ime/linux/fake_input_method_context.h" namespace ui { diff --git a/chromium/ui/base/ime/mock_input_method.cc b/chromium/ui/base/ime/mock_input_method.cc index 43d6b07f516..79ae727e400 100644 --- a/chromium/ui/base/ime/mock_input_method.cc +++ b/chromium/ui/base/ime/mock_input_method.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "ui/base/ime/mock_input_method.h" +#include "build/build_config.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/events/event.h" @@ -55,12 +56,14 @@ void MockInputMethod::OnBlur() { observer.OnBlur(); } -bool MockInputMethod::OnUntranslatedIMEMessage(const base::NativeEvent& event, +#if defined(OS_WIN) +bool MockInputMethod::OnUntranslatedIMEMessage(const MSG event, NativeEventResult* result) { if (result) *result = NativeEventResult(); return false; } +#endif void MockInputMethod::OnTextInputTypeChanged(const TextInputClient* client) { for (InputMethodObserver& observer : observer_list_) diff --git a/chromium/ui/base/ime/mock_input_method.h b/chromium/ui/base/ime/mock_input_method.h index b841a7a1d26..d3058a4608f 100644 --- a/chromium/ui/base/ime/mock_input_method.h +++ b/chromium/ui/base/ime/mock_input_method.h @@ -10,6 +10,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" #include "base/observer_list.h" +#include "build/build_config.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/input_method_observer.h" #include "ui/base/ime/ui_base_ime_export.h" @@ -32,8 +33,12 @@ class UI_BASE_IME_EXPORT MockInputMethod : public InputMethod { void SetDelegate(internal::InputMethodDelegate* delegate) override; void OnFocus() override; void OnBlur() override; - bool OnUntranslatedIMEMessage(const base::NativeEvent& event, + +#if defined(OS_WIN) + bool OnUntranslatedIMEMessage(const MSG event, NativeEventResult* result) override; +#endif + void SetFocusedTextInputClient(TextInputClient* client) override; void DetachTextInputClient(TextInputClient* client) override; TextInputClient* GetTextInputClient() const override; diff --git a/chromium/ui/base/ime/win/imm32_manager.cc b/chromium/ui/base/ime/win/imm32_manager.cc index ed9b186d6c6..9f9e6548840 100644 --- a/chromium/ui/base/ime/win/imm32_manager.cc +++ b/chromium/ui/base/ime/win/imm32_manager.cc @@ -78,13 +78,13 @@ void GetImeTextSpans(HIMC imm_context, ime_text_span.start_offset = clause_data[i]; ime_text_span.end_offset = clause_data[i + 1]; ime_text_span.underline_color = SK_ColorBLACK; - ime_text_span.thick = false; + ime_text_span.thickness = ui::ImeTextSpan::Thickness::kThin; ime_text_span.background_color = SK_ColorTRANSPARENT; // Use thick underline for the target clause. if (ime_text_span.start_offset >= static_cast<uint32_t>(target_start) && ime_text_span.end_offset <= static_cast<uint32_t>(target_end)) { - ime_text_span.thick = true; + ime_text_span.thickness = ui::ImeTextSpan::Thickness::kThick; } ime_text_spans->push_back(ime_text_span); } @@ -330,24 +330,24 @@ void IMM32Manager::GetCompositionInfo(HIMC imm_context, return; ImeTextSpan ime_text_span; - ime_text_span.underline_color = SK_ColorBLACK; + ime_text_span.underline_color = SK_ColorTRANSPARENT; ime_text_span.background_color = SK_ColorTRANSPARENT; if (target_start > 0) { ime_text_span.start_offset = 0U; ime_text_span.end_offset = static_cast<uint32_t>(target_start); - ime_text_span.thick = false; + ime_text_span.thickness = ui::ImeTextSpan::Thickness::kThin; composition->ime_text_spans.push_back(ime_text_span); } if (target_end > target_start) { ime_text_span.start_offset = static_cast<uint32_t>(target_start); ime_text_span.end_offset = static_cast<uint32_t>(target_end); - ime_text_span.thick = true; + ime_text_span.thickness = ui::ImeTextSpan::Thickness::kThick; composition->ime_text_spans.push_back(ime_text_span); } if (target_end < length) { ime_text_span.start_offset = static_cast<uint32_t>(target_end); ime_text_span.end_offset = static_cast<uint32_t>(length); - ime_text_span.thick = false; + ime_text_span.thickness = ui::ImeTextSpan::Thickness::kThin; composition->ime_text_spans.push_back(ime_text_span); } } diff --git a/chromium/ui/base/ime/win/mock_tsf_bridge.cc b/chromium/ui/base/ime/win/mock_tsf_bridge.cc new file mode 100644 index 00000000000..a69f37603e6 --- /dev/null +++ b/chromium/ui/base/ime/win/mock_tsf_bridge.cc @@ -0,0 +1,70 @@ +// Copyright 2018 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/win/mock_tsf_bridge.h" + +#include "base/logging.h" +#include "ui/base/ime/text_input_client.h" + +namespace ui { + +MockTSFBridge::MockTSFBridge() = default; + +MockTSFBridge::~MockTSFBridge() = default; + +bool MockTSFBridge::CancelComposition() { + ++cancel_composition_call_count_; + return true; +} + +bool MockTSFBridge::ConfirmComposition() { + ++confirm_composition_call_count_; + return true; +} + +void MockTSFBridge::OnTextInputTypeChanged(const TextInputClient* client) { + latest_text_input_type_ = client->GetTextInputType(); +} + +void MockTSFBridge::OnTextLayoutChanged() { + ++on_text_layout_changed_; +} + +void MockTSFBridge::SetFocusedClient(HWND focused_window, + TextInputClient* client) { + ++set_focused_client_call_count_; + focused_window_ = focused_window; + text_input_client_ = client; +} + +void MockTSFBridge::RemoveFocusedClient(TextInputClient* client) { + ++remove_focused_client_call_count_; + DCHECK_EQ(client, text_input_client_); + text_input_client_ = nullptr; + focused_window_ = nullptr; +} + +Microsoft::WRL::ComPtr<ITfThreadMgr> MockTSFBridge::GetThreadManager() { + return thread_manager_; +} + +TextInputClient* MockTSFBridge::GetFocusedTextInputClient() const { + return text_input_client_; +} + +void MockTSFBridge::Reset() { + enable_ime_call_count_ = 0; + disable_ime_call_count_ = 0; + cancel_composition_call_count_ = 0; + confirm_composition_call_count_ = 0; + on_text_layout_changed_ = 0; + associate_focus_call_count_ = 0; + set_focused_client_call_count_ = 0; + remove_focused_client_call_count_ = 0; + text_input_client_ = nullptr; + focused_window_ = nullptr; + latest_text_input_type_ = TEXT_INPUT_TYPE_NONE; +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/mock_tsf_bridge.h b/chromium/ui/base/ime/win/mock_tsf_bridge.h new file mode 100644 index 00000000000..5baca1ade05 --- /dev/null +++ b/chromium/ui/base/ime/win/mock_tsf_bridge.h @@ -0,0 +1,99 @@ +// Copyright 2018 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_WIN_MOCK_TSF_BRIDGE_H_ +#define UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_ + +#include <msctf.h> +#include <wrl/client.h> + +#include "base/compiler_specific.h" +#include "ui/base/ime/text_input_type.h" +#include "ui/base/ime/win/tsf_bridge.h" + +namespace ui { + +class MockTSFBridge : public TSFBridge { + public: + MockTSFBridge(); + ~MockTSFBridge() override; + + // TSFBridge: + bool CancelComposition() override; + bool ConfirmComposition() override; + void OnTextInputTypeChanged(const TextInputClient* client) override; + void OnTextLayoutChanged() override; + void SetFocusedClient(HWND focused_window, TextInputClient* client) override; + void RemoveFocusedClient(TextInputClient* client) override; + Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() override; + TextInputClient* GetFocusedTextInputClient() const override; + + // Resets MockTSFBridge state including function call counter. + void Reset(); + + // Call count of EnableIME(). + unsigned enable_ime_call_count() const { return enable_ime_call_count_; } + + // Call count of DisableIME(). + unsigned disable_ime_call_count() const { return disable_ime_call_count_; } + + // Call count of CancelComposition(). + unsigned cancel_composition_call_count() const { + return cancel_composition_call_count_; + } + + // Call count of ConfirmComposition(). + unsigned confirm_composition_call_count() const { + return confirm_composition_call_count_; + } + + // Call count of OnTextLayoutChanged(). + unsigned on_text_layout_changed() const { return on_text_layout_changed_; } + + // Call count of AssociateFocus(). + unsigned associate_focus_call_count() const { + return associate_focus_call_count_; + } + + // Call count of SetFocusClient(). + unsigned set_focused_client_call_count() const { + return set_focused_client_call_count_; + } + + // Call count of RemoveFocusedClient(). + unsigned remove_focused_client_call_count() const { + return remove_focused_client_call_count_; + } + + // Returns current TextInputClient. + TextInputClient* text_input_clinet() const { return text_input_client_; } + + // Returns currently focused window handle. + HWND focused_window() const { return focused_window_; } + + // Returns latest text input type. + TextInputType latest_text_iput_type() const { + return latest_text_input_type_; + } + + private: + unsigned enable_ime_call_count_ = 0; + unsigned disable_ime_call_count_ = 0; + unsigned cancel_composition_call_count_ = 0; + unsigned confirm_composition_call_count_ = 0; + unsigned on_text_layout_changed_ = 0; + unsigned associate_focus_call_count_ = 0; + unsigned set_focused_client_call_count_ = 0; + unsigned remove_focused_client_call_count_ = 0; + TextInputClient* text_input_client_ = nullptr; + HWND focused_window_ = nullptr; + TextInputType latest_text_input_type_ = TEXT_INPUT_TYPE_NONE; + Microsoft::WRL::ComPtr<ITfThreadMgr> thread_manager_; + + DISALLOW_COPY_AND_ASSIGN(MockTSFBridge); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_MOCK_TSF_BRIDGE_H_ diff --git a/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.cc b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.cc new file mode 100644 index 00000000000..ef3d5a82793 --- /dev/null +++ b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.cc @@ -0,0 +1,30 @@ +// Copyright 2018 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/win/on_screen_keyboard_display_manager_stub.h" + +namespace ui { + +// OnScreenKeyboardDisplayManagerStub member definitions. +OnScreenKeyboardDisplayManagerStub::OnScreenKeyboardDisplayManagerStub() {} + +OnScreenKeyboardDisplayManagerStub::~OnScreenKeyboardDisplayManagerStub() {} + +bool OnScreenKeyboardDisplayManagerStub::DisplayVirtualKeyboard( + OnScreenKeyboardObserver* observer) { + return false; +} + +bool OnScreenKeyboardDisplayManagerStub::DismissVirtualKeyboard() { + return false; +} + +void OnScreenKeyboardDisplayManagerStub::RemoveObserver( + OnScreenKeyboardObserver* observer) {} + +bool OnScreenKeyboardDisplayManagerStub::IsKeyboardVisible() const { + return false; +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.h b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.h new file mode 100644 index 00000000000..184f8067a1e --- /dev/null +++ b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.h @@ -0,0 +1,35 @@ +// Copyright 2018 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_WIN_ON_SCREEN_KEYBOARD_DISPLAY_MANAGER_STUB_H_ +#define UI_BASE_IME_WIN_ON_SCREEN_KEYBOARD_DISPLAY_MANAGER_STUB_H_ + +#include "ui/base/ime/ui_base_ime_export.h" +#include "ui/base/ime/win/osk_display_manager.h" + +namespace ui { + +// This class provides a stub OnScreenDisplayManager. +// Used for < Win8 OS versions. +class UI_BASE_IME_EXPORT OnScreenKeyboardDisplayManagerStub + : public OnScreenKeyboardDisplayManager { + public: + ~OnScreenKeyboardDisplayManagerStub() override; + + // OnScreenKeyboardDisplayManager overrides. + bool DisplayVirtualKeyboard(OnScreenKeyboardObserver* observer) final; + bool DismissVirtualKeyboard() final; + void RemoveObserver(OnScreenKeyboardObserver* observer) final; + bool IsKeyboardVisible() const final; + + private: + friend class OnScreenKeyboardDisplayManager; + OnScreenKeyboardDisplayManagerStub(); + + DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboardDisplayManagerStub); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_ON_SCREEN_KEYBOARD_DISPLAY_MANAGER_STUB_H_ diff --git a/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.cc b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.cc new file mode 100644 index 00000000000..f1fcd63e3c1 --- /dev/null +++ b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.cc @@ -0,0 +1,392 @@ +// Copyright 2016 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/win/on_screen_keyboard_display_manager_tab_tip.h" + +#include <windows.h> + +#include <shellapi.h> +#include <shlobj.h> +#include <shobjidl.h> // Must be before propkey. + +#include "base/bind.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_task_runner_handle.h" +#include "base/win/registry.h" +#include "base/win/scoped_co_mem.h" +#include "base/win/win_util.h" +#include "base/win/windows_version.h" +#include "ui/base/ime/win/osk_display_observer.h" +#include "ui/base/win/hidden_window.h" +#include "ui/display/win/screen_win.h" +#include "ui/gfx/geometry/dip_util.h" + +namespace { + +constexpr base::TimeDelta kCheckOSKDelay = + base::TimeDelta::FromMilliseconds(1000); +constexpr base::TimeDelta kDismissKeyboardRetryTimeout = + base::TimeDelta::FromMilliseconds(100); +constexpr int kDismissKeyboardMaxRetries = 5; + +constexpr wchar_t kOSKClassName[] = L"IPTip_Main_Window"; + +constexpr wchar_t kWindows8OSKRegPath[] = + L"Software\\Classes\\CLSID\\{054AAE20-4BEA-4347-8A35-64A533254A9D}" + L"\\LocalServer32"; + +} // namespace + +namespace ui { + +// This class provides functionality to detect when the on screen keyboard +// is displayed and move the main window up if it is obscured by the keyboard. +class OnScreenKeyboardDetector { + public: + OnScreenKeyboardDetector(); + ~OnScreenKeyboardDetector(); + + // Schedules a delayed task which detects if the on screen keyboard was + // displayed. + void DetectKeyboard(HWND main_window); + + // Dismisses the on screen keyboard. If a call to display the keyboard was + // made, this function waits for the keyboard to become visible by retrying + // upto a maximum of kDismissKeyboardMaxRetries. + bool DismissKeyboard(); + + // Add/Remove keyboard observers. + // Please note that this class does not track the |observer| destruction. It + // is upto the classes which set up these observers to remove them when they + // are destroyed. + void AddObserver(OnScreenKeyboardObserver* observer); + void RemoveObserver(OnScreenKeyboardObserver* observer); + + // Returns true if the osk is visible. + static bool IsKeyboardVisible(); + + private: + // Returns the occluded rect in dips. + gfx::Rect GetOccludedRect(); + + // Executes as a task and detects if the on screen keyboard is displayed. + // Once the keyboard is displayed it schedules the HideIfNecessary() task to + // detect when the keyboard is or should be hidden. + void CheckIfKeyboardVisible(); + + // Executes as a task and detects if the keyboard was hidden or should be + // hidden. + void HideIfNecessary(); + + // Notifies observers that the keyboard was displayed. + // A recurring task HideIfNecessary() is started to detect when the OSK + // disappears. + void HandleKeyboardVisible(const gfx::Rect& occluded_rect); + + // Notifies observers that the keyboard was hidden. + // The observer list is cleared out after this notification. + void HandleKeyboardHidden(); + + // Removes all observers from the list. + void ClearObservers(); + + // The main window which displays the on screen keyboard. + HWND main_window_ = nullptr; + + // Tracks if the keyboard was displayed. + bool osk_visible_notification_received_ = false; + + // Set to true if a call to DetectKeyboard() was made. + bool keyboard_detect_requested_ = false; + + // Contains the number of attempts made to dismiss the keyboard. Please refer + // to the DismissKeyboard() function for more information. + int keyboard_dismiss_retry_count_ = 0; + + base::ObserverList<OnScreenKeyboardObserver, false> observers_; + + // Should be the last member in the class. Helps ensure that tasks spawned + // by this class instance are canceled when it is destroyed. + base::WeakPtrFactory<OnScreenKeyboardDetector> keyboard_detector_factory_; + + DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboardDetector); +}; + +// OnScreenKeyboardDetector member definitions. +OnScreenKeyboardDetector::OnScreenKeyboardDetector() + : keyboard_detector_factory_(this) {} + +OnScreenKeyboardDetector::~OnScreenKeyboardDetector() { + ClearObservers(); +} + +void OnScreenKeyboardDetector::DetectKeyboard(HWND main_window) { + main_window_ = main_window; + keyboard_detect_requested_ = true; + // The keyboard is displayed by TabTip.exe which is launched via a + // ShellExecute call in the + // OnScreenKeyboardDisplayManager::DisplayVirtualKeyboard() function. We use + // a delayed task to check if the keyboard is visible because of the possible + // delay between the ShellExecute call and the keyboard becoming visible. + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&OnScreenKeyboardDetector::CheckIfKeyboardVisible, + keyboard_detector_factory_.GetWeakPtr()), + kCheckOSKDelay); +} + +bool OnScreenKeyboardDetector::DismissKeyboard() { + // We dismiss the virtual keyboard by generating the SC_CLOSE. + HWND osk = ::FindWindow(kOSKClassName, nullptr); + if (::IsWindow(osk) && ::IsWindowEnabled(osk)) { + keyboard_detect_requested_ = false; + keyboard_dismiss_retry_count_ = 0; + HandleKeyboardHidden(); + PostMessage(osk, WM_SYSCOMMAND, SC_CLOSE, 0); + return true; + } + + if (keyboard_detect_requested_) { + if (keyboard_dismiss_retry_count_ < kDismissKeyboardMaxRetries) { + keyboard_dismiss_retry_count_++; + // Please refer to the comments in the DetectKeyboard() function for more + // information as to why we need a delayed task here. + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce( + base::IgnoreResult(&OnScreenKeyboardDetector::DismissKeyboard), + keyboard_detector_factory_.GetWeakPtr()), + kDismissKeyboardRetryTimeout); + } else { + keyboard_dismiss_retry_count_ = 0; + } + } + return false; +} + +void OnScreenKeyboardDetector::AddObserver(OnScreenKeyboardObserver* observer) { + observers_.AddObserver(observer); +} + +void OnScreenKeyboardDetector::RemoveObserver( + OnScreenKeyboardObserver* observer) { + observers_.RemoveObserver(observer); +} + +// static +bool OnScreenKeyboardDetector::IsKeyboardVisible() { + HWND osk = ::FindWindow(kOSKClassName, nullptr); + if (!::IsWindow(osk)) + return false; + return ::IsWindowVisible(osk) && ::IsWindowEnabled(osk); +} + +gfx::Rect OnScreenKeyboardDetector::GetOccludedRect() { + gfx::Rect occluded_rect; + HWND osk = ::FindWindow(kOSKClassName, nullptr); + if (!::IsWindow(osk) || !::IsWindowVisible(osk) || !::IsWindowEnabled(osk)) + return occluded_rect; + + RECT osk_rect = {}; + RECT main_window_rect = {}; + if (!::GetWindowRect(osk, &osk_rect) || + !::GetWindowRect(main_window_, &main_window_rect)) { + return occluded_rect; + } + + gfx::Rect gfx_osk_rect(osk_rect); + gfx::Rect gfx_main_window_rect(main_window_rect); + + gfx_osk_rect.Intersect(gfx_main_window_rect); + + return display::win::ScreenWin::ScreenToDIPRect(main_window_, gfx_osk_rect); +} + +void OnScreenKeyboardDetector::CheckIfKeyboardVisible() { + gfx::Rect occluded_rect = GetOccludedRect(); + if (!occluded_rect.IsEmpty()) { + if (!osk_visible_notification_received_) + HandleKeyboardVisible(occluded_rect); + } else { + DVLOG(1) << "OSK did not come up. Something wrong."; + } +} + +void OnScreenKeyboardDetector::HideIfNecessary() { + HWND osk = ::FindWindow(kOSKClassName, nullptr); + if (!::IsWindow(osk)) + return; + + // Three cases here. + // 1. OSK was hidden because the user dismissed it. + // 2. We are no longer in the foreground. + // 3. The OSK is still visible. + // In the first case we just have to notify the observers that the OSK was + // hidden. + // In the second case we need to dismiss the OSK which internally will + // notify the observers about the OSK being hidden. + if (!::IsWindowEnabled(osk)) { + if (osk_visible_notification_received_) { + if (main_window_ == ::GetForegroundWindow()) { + DVLOG(1) << "OSK window hidden while we are in the foreground."; + HandleKeyboardHidden(); + } + } + } else if (main_window_ != ::GetForegroundWindow()) { + if (osk_visible_notification_received_) { + DVLOG(1) << "We are no longer in the foreground. Dismising OSK."; + DismissKeyboard(); + } + } else { + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&OnScreenKeyboardDetector::HideIfNecessary, + keyboard_detector_factory_.GetWeakPtr()), + kCheckOSKDelay); + } +} + +void OnScreenKeyboardDetector::HandleKeyboardVisible( + const gfx::Rect& occluded_rect) { + DCHECK(!osk_visible_notification_received_); + osk_visible_notification_received_ = true; + + for (OnScreenKeyboardObserver& observer : observers_) + observer.OnKeyboardVisible(occluded_rect); + + // Now that the keyboard is visible, run the task to detect if it was hidden. + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&OnScreenKeyboardDetector::HideIfNecessary, + keyboard_detector_factory_.GetWeakPtr()), + kCheckOSKDelay); +} + +void OnScreenKeyboardDetector::HandleKeyboardHidden() { + osk_visible_notification_received_ = false; + for (OnScreenKeyboardObserver& observer : observers_) + observer.OnKeyboardHidden(); + ClearObservers(); +} + +void OnScreenKeyboardDetector::ClearObservers() { + for (auto& observer : observers_) + RemoveObserver(&observer); +} + +// OnScreenKeyboardDisplayManagerTabTip member definitions. +OnScreenKeyboardDisplayManagerTabTip::OnScreenKeyboardDisplayManagerTabTip() { + DCHECK_GE(base::win::GetVersion(), base::win::VERSION_WIN8); +} + +OnScreenKeyboardDisplayManagerTabTip::~OnScreenKeyboardDisplayManagerTabTip() {} + +bool OnScreenKeyboardDisplayManagerTabTip::DisplayVirtualKeyboard( + OnScreenKeyboardObserver* observer) { + if (base::win::IsKeyboardPresentOnSlate(nullptr, ui::GetHiddenWindow())) + return false; + + if (osk_path_.empty() && !GetOSKPath(&osk_path_)) { + DLOG(WARNING) << "Failed to get on screen keyboard path from registry"; + return false; + } + + HINSTANCE ret = ::ShellExecuteW(nullptr, L"", osk_path_.c_str(), nullptr, + nullptr, SW_SHOW); + + bool success = reinterpret_cast<intptr_t>(ret) > 32; + if (success) { + // If multiple calls to DisplayVirtualKeyboard occur one after the other, + // the last observer would be the one to get notifications. + keyboard_detector_ = std::make_unique<OnScreenKeyboardDetector>(); + if (observer) + keyboard_detector_->AddObserver(observer); + keyboard_detector_->DetectKeyboard(::GetForegroundWindow()); + } + return success; +} + +bool OnScreenKeyboardDisplayManagerTabTip::DismissVirtualKeyboard() { + return keyboard_detector_ ? keyboard_detector_->DismissKeyboard() : false; +} + +void OnScreenKeyboardDisplayManagerTabTip::RemoveObserver( + OnScreenKeyboardObserver* observer) { + if (keyboard_detector_) + keyboard_detector_->RemoveObserver(observer); +} + +bool OnScreenKeyboardDisplayManagerTabTip::GetOSKPath( + base::string16* osk_path) { + DCHECK(osk_path); + + // We need to launch TabTip.exe from the location specified under the + // LocalServer32 key for the {{054AAE20-4BEA-4347-8A35-64A533254A9D}} + // CLSID. + // TabTip.exe is typically found at + // c:\program files\common files\microsoft shared\ink on English Windows. + // We don't want to launch TabTip.exe from + // c:\program files (x86)\common files\microsoft shared\ink. This path is + // normally found on 64 bit Windows. + base::win::RegKey key(HKEY_LOCAL_MACHINE, kWindows8OSKRegPath, + KEY_READ | KEY_WOW64_64KEY); + DWORD osk_path_length = 1024; + if (key.ReadValue(nullptr, base::WriteInto(osk_path, osk_path_length), + &osk_path_length, nullptr) != ERROR_SUCCESS) { + return false; + } + + osk_path->resize(base::string16::traits_type::length(osk_path->c_str())); + + *osk_path = base::ToLowerASCII(*osk_path); + + size_t common_program_files_offset = osk_path->find(L"%commonprogramfiles%"); + // Typically the path to TabTip.exe read from the registry will start with + // %CommonProgramFiles% which needs to be replaced with the corrsponding + // expanded string. + // If the path does not begin with %CommonProgramFiles% we use it as is. + if (common_program_files_offset != base::string16::npos) { + // Preserve the beginning quote in the path. + osk_path->erase(common_program_files_offset, + wcslen(L"%commonprogramfiles%")); + // The path read from the registry contains the %CommonProgramFiles% + // environment variable prefix. On 64 bit Windows the SHGetKnownFolderPath + // function returns the common program files path with the X86 suffix for + // the FOLDERID_ProgramFilesCommon value. + // To get the correct path to TabTip.exe we first read the environment + // variable CommonProgramW6432 which points to the desired common + // files path. Failing that we fallback to the SHGetKnownFolderPath API. + + // We then replace the %CommonProgramFiles% value with the actual common + // files path found in the process. + base::string16 common_program_files_path; + DWORD buffer_size = + GetEnvironmentVariable(L"CommonProgramW6432", nullptr, 0); + if (buffer_size) { + GetEnvironmentVariable( + L"CommonProgramW6432", + base::WriteInto(&common_program_files_path, buffer_size), + buffer_size); + DCHECK(!common_program_files_path.empty()); + } else { + base::win::ScopedCoMem<wchar_t> common_program_files; + if (FAILED(SHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr, + &common_program_files))) { + return false; + } + common_program_files_path = common_program_files; + } + osk_path->insert(common_program_files_offset, common_program_files_path); + } + return !osk_path->empty(); +} + +bool OnScreenKeyboardDisplayManagerTabTip::IsKeyboardVisible() const { + return OnScreenKeyboardDetector::IsKeyboardVisible(); +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.h b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.h new file mode 100644 index 00000000000..e63bf814456 --- /dev/null +++ b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.h @@ -0,0 +1,51 @@ +// Copyright 2016 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_WIN_ON_SCREEN_KEYBOARD_DISPLAY_MANAGER_TAB_TIP_H_ +#define UI_BASE_IME_WIN_ON_SCREEN_KEYBOARD_DISPLAY_MANAGER_TAB_TIP_H_ + +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "ui/base/ime/ui_base_ime_export.h" +#include "ui/base/ime/win/osk_display_manager.h" + +namespace ui { + +class OnScreenKeyboardDetector; + +// This class provides an implementation of the OnScreenKeyboardDisplayManager +// that uses heuristics and the TabTip.exe to display the on screen keyboard. +// Used on Windows > 7 and Windows < 10.0.10240.0 +class UI_BASE_IME_EXPORT OnScreenKeyboardDisplayManagerTabTip + : public OnScreenKeyboardDisplayManager { + public: + ~OnScreenKeyboardDisplayManagerTabTip() override; + + // OnScreenKeyboardDisplayManager overrides. + bool DisplayVirtualKeyboard(OnScreenKeyboardObserver* observer) final; + bool DismissVirtualKeyboard() final; + void RemoveObserver(OnScreenKeyboardObserver* observer) final; + bool IsKeyboardVisible() const final; + + // Returns the path of the on screen keyboard exe (TabTip.exe) in the + // |osk_path| parameter. + // Returns true on success. + bool GetOSKPath(base::string16* osk_path); + + private: + friend class OnScreenKeyboardDisplayManager; + friend class OnScreenKeyboardTest; + OnScreenKeyboardDisplayManagerTabTip(); + + std::unique_ptr<OnScreenKeyboardDetector> keyboard_detector_; + + // The location of TabTip.exe. + base::string16 osk_path_; + + DISALLOW_COPY_AND_ASSIGN(OnScreenKeyboardDisplayManagerTabTip); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_ON_SCREEN_KEYBOARD_DISPLAY_MANAGER_TAB_TIP_H_ diff --git a/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_unittest.cc b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_unittest.cc new file mode 100644 index 00000000000..16d785f01a8 --- /dev/null +++ b/chromium/ui/base/ime/win/on_screen_keyboard_display_manager_unittest.cc @@ -0,0 +1,53 @@ +// Copyright 2016 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/win/on_screen_keyboard_display_manager_tab_tip.h" + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/strings/string16.h" +#include "base/win/windows_version.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace ui { + +class OnScreenKeyboardTest : public ::testing::Test { + protected: + std::unique_ptr<OnScreenKeyboardDisplayManagerTabTip> + CreateOSKDisplayManager() { + return std::unique_ptr<OnScreenKeyboardDisplayManagerTabTip>( + new OnScreenKeyboardDisplayManagerTabTip()); + } +}; + +// This test validates the on screen keyboard path (tabtip.exe) which is read +// from the registry. +TEST_F(OnScreenKeyboardTest, OSKPath) { + // The on screen keyboard is only available on Windows 8+. + if (base::win::GetVersion() < base::win::VERSION_WIN8) + return; + + std::unique_ptr<OnScreenKeyboardDisplayManagerTabTip> + keyboard_display_manager(CreateOSKDisplayManager()); + EXPECT_NE(nullptr, keyboard_display_manager); + + base::string16 osk_path; + EXPECT_TRUE(keyboard_display_manager->GetOSKPath(&osk_path)); + EXPECT_FALSE(osk_path.empty()); + EXPECT_TRUE(osk_path.find(L"tabtip.exe") != base::string16::npos); + + // The path read from the registry can be quoted. To check for the existence + // of the file we use the base::PathExists function which internally uses the + // GetFileAttributes API which does not accept quoted strings. Our workaround + // is to look for quotes in the first and last position in the string and + // erase them. + if ((osk_path.front() == L'"') && (osk_path.back() == L'"')) + osk_path = osk_path.substr(1, osk_path.size() - 2); + + EXPECT_TRUE(base::PathExists(base::FilePath(osk_path))); +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/osk_display_manager.cc b/chromium/ui/base/ime/win/osk_display_manager.cc new file mode 100644 index 00000000000..e7b441c0eb1 --- /dev/null +++ b/chromium/ui/base/ime/win/osk_display_manager.cc @@ -0,0 +1,31 @@ +// Copyright 2016 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/win/osk_display_manager.h" + +#include "base/debug/leak_annotations.h" +#include "base/win/windows_version.h" +#include "ui/base/ime/win/on_screen_keyboard_display_manager_stub.h" +#include "ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.h" + +namespace ui { + +// OnScreenKeyboardDisplayManager member definitions. +OnScreenKeyboardDisplayManager::OnScreenKeyboardDisplayManager() {} + +OnScreenKeyboardDisplayManager::~OnScreenKeyboardDisplayManager() {} + +OnScreenKeyboardDisplayManager* OnScreenKeyboardDisplayManager::GetInstance() { + static OnScreenKeyboardDisplayManager* instance = nullptr; + if (!instance) { + if (base::win::GetVersion() < base::win::VERSION_WIN8) + instance = new OnScreenKeyboardDisplayManagerStub(); + else + instance = new OnScreenKeyboardDisplayManagerTabTip(); + ANNOTATE_LEAKING_OBJECT_PTR(instance); + } + return instance; +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/osk_display_manager.h b/chromium/ui/base/ime/win/osk_display_manager.h new file mode 100644 index 00000000000..489558669f3 --- /dev/null +++ b/chromium/ui/base/ime/win/osk_display_manager.h @@ -0,0 +1,47 @@ +// Copyright 2016 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_WIN_OSK_DISPLAY_MANAGER_H_ +#define UI_BASE_IME_WIN_OSK_DISPLAY_MANAGER_H_ + +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/strings/string16.h" +#include "ui/base/ime/ui_base_ime_export.h" +#include "ui/gfx/geometry/rect.h" + +namespace ui { + +class OnScreenKeyboardObserver; + +// This class provides functionality to display the on screen keyboard on +// Windows 8+. It optionally notifies observers that the OSK is displayed, +// hidden, etc. +class UI_BASE_IME_EXPORT OnScreenKeyboardDisplayManager { + public: + static OnScreenKeyboardDisplayManager* GetInstance(); + + virtual ~OnScreenKeyboardDisplayManager(); + + // Functions to display and dismiss the keyboard. + // The optional |observer| parameter allows callers to be notified when the + // keyboard is displayed, dismissed, etc. + virtual bool DisplayVirtualKeyboard(OnScreenKeyboardObserver* observer) = 0; + // When the keyboard is dismissed, the registered observer if any is removed + // after notifying it. + virtual bool DismissVirtualKeyboard() = 0; + + // Removes a registered observer. + virtual void RemoveObserver(OnScreenKeyboardObserver* observer) = 0; + + // Returns true if the virtual keyboard is currently visible. + virtual bool IsKeyboardVisible() const = 0; + + protected: + OnScreenKeyboardDisplayManager(); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_OSK_DISPLAY_MANAGER_H_ diff --git a/chromium/ui/base/ime/win/osk_display_observer.h b/chromium/ui/base/ime/win/osk_display_observer.h new file mode 100644 index 00000000000..8c83f567505 --- /dev/null +++ b/chromium/ui/base/ime/win/osk_display_observer.h @@ -0,0 +1,27 @@ +// Copyright 2016 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_WIN_OSK_DISPLAY_OBSERVER_H_ +#define UI_BASE_IME_WIN_OSK_DISPLAY_OBSERVER_H_ + +namespace gfx { +class Rect; +} + +namespace ui { + +// Implemented by classes who wish to get notified about the on screen keyboard +// becoming visible/hidden. +class UI_BASE_IME_EXPORT OnScreenKeyboardObserver { + public: + virtual ~OnScreenKeyboardObserver() {} + + // The |keyboard_rect| parameter contains the bounds of the keyboard in dips. + virtual void OnKeyboardVisible(const gfx::Rect& keyboard_rect) {} + virtual void OnKeyboardHidden() {} +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_OSK_DISPLAY_OBSERVER_H_ diff --git a/chromium/ui/base/ime/win/tsf_bridge.cc b/chromium/ui/base/ime/win/tsf_bridge.cc new file mode 100644 index 00000000000..b5b1ba2a38d --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_bridge.cc @@ -0,0 +1,542 @@ +// Copyright 2018 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 <msctf.h> + +#include <map> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/no_destructor.h" +#include "base/threading/thread_local_storage.h" +#include "base/win/scoped_variant.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/base/ime/win/tsf_bridge.h" +#include "ui/base/ime/win/tsf_text_store.h" +#include "ui/base/ui_base_features.h" + +namespace ui { + +namespace { + +// TSFBridgeImpl ----------------------------------------------------------- + +// A TLS implementation of TSFBridge. +class TSFBridgeImpl : public TSFBridge { + public: + TSFBridgeImpl(); + ~TSFBridgeImpl() override; + + bool Initialize(); + + // TsfBridge: + void OnTextInputTypeChanged(const TextInputClient* client) override; + void OnTextLayoutChanged() override; + bool CancelComposition() override; + bool ConfirmComposition() override; + void SetFocusedClient(HWND focused_window, TextInputClient* client) override; + void RemoveFocusedClient(TextInputClient* client) override; + Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() override; + TextInputClient* GetFocusedTextInputClient() const override; + + private: + // Returns true if |tsf_document_map_| is successfully initialized. This + // method should be called from and only from Initialize(). + bool InitializeDocumentMapInternal(); + + // Returns true if |context| is successfully updated to be a disabled + // context, where an IME should be deactivated. This is suitable for some + // special input context such as password fields. + bool InitializeDisabledContext(ITfContext* context); + + // Returns true if a TSF document manager and a TSF context is successfully + // created with associating with given |text_store|. The returned + // |source_cookie| indicates the binding between |text_store| and |context|. + // You can pass nullptr to |text_store| and |source_cookie| when text store is + // not necessary. + bool CreateDocumentManager(TSFTextStore* text_store, + ITfDocumentMgr** document_manager, + ITfContext** context, + DWORD* source_cookie); + + // Returns true if |document_manager| is the focused document manager. + bool IsFocused(ITfDocumentMgr* document_manager); + + // Returns true if already initialized. + bool IsInitialized(); + + // Updates or clears the association maintained in the TSF runtime between + // |attached_window_handle_| and the current document manager. Keeping this + // association updated solves some tricky event ordering issues between + // logical text input focus managed by Chrome and native text input focus + // managed by the OS. + // Background: + // TSF runtime monitors some Win32 messages such as WM_ACTIVATE to + // change the focused document manager. This is problematic when + // TSFBridge::SetFocusedClient is called first then the target window + // receives WM_ACTIVATE. This actually occurs in Aura environment where + // WM_NCACTIVATE is used as a trigger to restore text input focus. + // Caveats: + // TSF runtime does not increment the reference count of the attached + // document manager. See the comment inside the method body for + // details. + void UpdateAssociateFocus(); + void ClearAssociateFocus(); + + // A triple of document manager, text store and binding cookie between + // a context owned by the document manager and the text store. This is a + // minimum working set of an editable document in TSF. + struct TSFDocument { + public: + TSFDocument() : cookie(TF_INVALID_COOKIE) {} + TSFDocument(const TSFDocument& src) + : document_manager(src.document_manager), cookie(src.cookie) {} + Microsoft::WRL::ComPtr<ITfDocumentMgr> document_manager; + scoped_refptr<TSFTextStore> text_store; + DWORD cookie; + }; + + // Returns a pointer to TSFDocument that is associated with the current + // TextInputType of |client_|. + TSFDocument* GetAssociatedDocument(); + + // An ITfThreadMgr object to be used in focus and document management. + Microsoft::WRL::ComPtr<ITfThreadMgr> thread_manager_; + + // A map from TextInputType to an editable document for TSF. We use multiple + // TSF documents that have different InputScopes and TSF attributes based on + // the TextInputType associated with the target document. For a TextInputType + // that is not coverted by this map, a default document, e.g. the document + // for TEXT_INPUT_TYPE_TEXT, should be used. + // Note that some IMEs don't change their state unless the document focus is + // changed. This is why we use multiple documents instead of changing TSF + // metadata of a single document on the fly. + typedef std::map<TextInputType, TSFDocument> TSFDocumentMap; + TSFDocumentMap tsf_document_map_; + + // An identifier of TSF client. + TfClientId client_id_ = TF_CLIENTID_NULL; + + // Current focused text input client. Do not free |client_|. + TextInputClient* client_ = nullptr; + + // Represents the window that is currently owns text input focus. + HWND attached_window_handle_ = nullptr; + + DISALLOW_COPY_AND_ASSIGN(TSFBridgeImpl); +}; + +TSFBridgeImpl::TSFBridgeImpl() = default; + +TSFBridgeImpl::~TSFBridgeImpl() { + DCHECK(base::MessageLoopForUI::IsCurrent()); + if (!IsInitialized()) + return; + for (TSFDocumentMap::iterator it = tsf_document_map_.begin(); + it != tsf_document_map_.end(); ++it) { + Microsoft::WRL::ComPtr<ITfContext> context; + Microsoft::WRL::ComPtr<ITfSource> source; + if (it->second.cookie != TF_INVALID_COOKIE && + SUCCEEDED( + it->second.document_manager->GetBase(context.GetAddressOf())) && + SUCCEEDED(context.CopyTo(source.GetAddressOf()))) { + source->UnadviseSink(it->second.cookie); + } + } + tsf_document_map_.clear(); + + client_id_ = TF_CLIENTID_NULL; +} + +bool TSFBridgeImpl::Initialize() { + DCHECK(base::MessageLoopForUI::IsCurrent()); + if (client_id_ != TF_CLIENTID_NULL) { + DVLOG(1) << "Already initialized."; + return false; + } + + if (FAILED(::CoCreateInstance(CLSID_TF_ThreadMgr, nullptr, CLSCTX_ALL, + IID_PPV_ARGS(&thread_manager_)))) { + DVLOG(1) << "Failed to create ThreadManager instance."; + return false; + } + + if (FAILED(thread_manager_->Activate(&client_id_))) { + DVLOG(1) << "Failed to activate Thread Manager."; + return false; + } + + if (!InitializeDocumentMapInternal()) + return false; + + // Japanese IME expects the default value of this compartment is + // TF_SENTENCEMODE_PHRASEPREDICT like IMM32 implementation. This value is + // managed per thread, so that it is enough to set this value at once. This + // value does not affect other language's IME behaviors. + Microsoft::WRL::ComPtr<ITfCompartmentMgr> thread_compartment_manager; + if (FAILED( + thread_manager_.CopyTo(thread_compartment_manager.GetAddressOf()))) { + DVLOG(1) << "Failed to get ITfCompartmentMgr."; + return false; + } + + Microsoft::WRL::ComPtr<ITfCompartment> sentence_compartment; + if (FAILED(thread_compartment_manager->GetCompartment( + GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE, + sentence_compartment.GetAddressOf()))) { + DVLOG(1) << "Failed to get sentence compartment."; + return false; + } + + base::win::ScopedVariant sentence_variant; + sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT); + if (FAILED( + sentence_compartment->SetValue(client_id_, sentence_variant.ptr()))) { + DVLOG(1) << "Failed to change the sentence mode."; + return false; + } + + return true; +} + +void TSFBridgeImpl::OnTextInputTypeChanged(const TextInputClient* client) { + DCHECK(base::MessageLoopForUI::IsCurrent()); + DCHECK(IsInitialized()); + + if (client != client_) { + // Called from not focusing client. Do nothing. + return; + } + + UpdateAssociateFocus(); + + TSFDocument* document = GetAssociatedDocument(); + if (!document) + return; + thread_manager_->SetFocus(document->document_manager.Get()); + OnTextLayoutChanged(); +} + +void TSFBridgeImpl::OnTextLayoutChanged() { + TSFDocument* document = GetAssociatedDocument(); + if (!document) + return; + if (!document->text_store) + return; + document->text_store->SendOnLayoutChange(); +} + +bool TSFBridgeImpl::CancelComposition() { + DCHECK(base::MessageLoopForUI::IsCurrent()); + DCHECK(IsInitialized()); + + TSFDocument* document = GetAssociatedDocument(); + if (!document) + return false; + if (!document->text_store) + return false; + + return document->text_store->CancelComposition(); +} + +bool TSFBridgeImpl::ConfirmComposition() { + DCHECK(base::MessageLoopForUI::IsCurrent()); + DCHECK(IsInitialized()); + + TSFDocument* document = GetAssociatedDocument(); + if (!document) + return false; + if (!document->text_store) + return false; + + return document->text_store->ConfirmComposition(); +} + +void TSFBridgeImpl::SetFocusedClient(HWND focused_window, + TextInputClient* client) { + DCHECK(base::MessageLoopForUI::IsCurrent()); + DCHECK(client); + DCHECK(IsInitialized()); + if (attached_window_handle_ != focused_window) + ClearAssociateFocus(); + client_ = client; + attached_window_handle_ = focused_window; + + for (TSFDocumentMap::iterator it = tsf_document_map_.begin(); + it != tsf_document_map_.end(); ++it) { + if (it->second.text_store.get() == nullptr) + continue; + it->second.text_store->SetFocusedTextInputClient(focused_window, client); + } + + // Synchronize text input type state. + OnTextInputTypeChanged(client); +} + +void TSFBridgeImpl::RemoveFocusedClient(TextInputClient* client) { + DCHECK(base::MessageLoopForUI::IsCurrent()); + DCHECK(IsInitialized()); + if (client_ != client) + return; + ClearAssociateFocus(); + client_ = nullptr; + attached_window_handle_ = nullptr; + for (TSFDocumentMap::iterator it = tsf_document_map_.begin(); + it != tsf_document_map_.end(); ++it) { + if (it->second.text_store.get() == nullptr) + continue; + it->second.text_store->SetFocusedTextInputClient(nullptr, nullptr); + } +} + +TextInputClient* TSFBridgeImpl::GetFocusedTextInputClient() const { + return client_; +} + +Microsoft::WRL::ComPtr<ITfThreadMgr> TSFBridgeImpl::GetThreadManager() { + DCHECK(base::MessageLoopForUI::IsCurrent()); + DCHECK(IsInitialized()); + return thread_manager_; +} + +bool TSFBridgeImpl::CreateDocumentManager(TSFTextStore* text_store, + ITfDocumentMgr** document_manager, + ITfContext** context, + DWORD* source_cookie) { + if (FAILED(thread_manager_->CreateDocumentMgr(document_manager))) { + DVLOG(1) << "Failed to create Document Manager."; + return false; + } + + DWORD edit_cookie = TF_INVALID_EDIT_COOKIE; + if (FAILED((*document_manager) + ->CreateContext(client_id_, 0, + static_cast<ITextStoreACP*>(text_store), + context, &edit_cookie))) { + DVLOG(1) << "Failed to create Context."; + return false; + } + + if (FAILED((*document_manager)->Push(*context))) { + DVLOG(1) << "Failed to push context."; + return false; + } + + if (!text_store || !source_cookie) + return true; + + Microsoft::WRL::ComPtr<ITfSource> source; + if (FAILED((*context)->QueryInterface(IID_PPV_ARGS(&source)))) { + DVLOG(1) << "Failed to get source."; + return false; + } + + if (FAILED(source->AdviseSink(IID_ITfTextEditSink, + static_cast<ITfTextEditSink*>(text_store), + source_cookie))) { + DVLOG(1) << "AdviseSink failed."; + return false; + } + + if (*source_cookie == TF_INVALID_COOKIE) { + DVLOG(1) << "The result of cookie is invalid."; + return false; + } + return true; +} + +bool TSFBridgeImpl::InitializeDocumentMapInternal() { + const TextInputType kTextInputTypes[] = { + TEXT_INPUT_TYPE_NONE, TEXT_INPUT_TYPE_TEXT, + TEXT_INPUT_TYPE_PASSWORD, TEXT_INPUT_TYPE_SEARCH, + TEXT_INPUT_TYPE_EMAIL, TEXT_INPUT_TYPE_NUMBER, + TEXT_INPUT_TYPE_TELEPHONE, TEXT_INPUT_TYPE_URL, + }; + for (size_t i = 0; i < arraysize(kTextInputTypes); ++i) { + const TextInputType input_type = kTextInputTypes[i]; + Microsoft::WRL::ComPtr<ITfContext> context; + Microsoft::WRL::ComPtr<ITfDocumentMgr> document_manager; + DWORD cookie = TF_INVALID_COOKIE; + const bool use_null_text_store = (input_type == TEXT_INPUT_TYPE_NONE); + DWORD* cookie_ptr = use_null_text_store ? nullptr : &cookie; + scoped_refptr<TSFTextStore> text_store = + use_null_text_store ? nullptr : new TSFTextStore(); + if (!CreateDocumentManager(text_store.get(), + document_manager.GetAddressOf(), + context.GetAddressOf(), cookie_ptr)) + return false; + const bool use_disabled_context = (input_type == TEXT_INPUT_TYPE_PASSWORD || + input_type == TEXT_INPUT_TYPE_NONE); + if (use_disabled_context && !InitializeDisabledContext(context.Get())) + return false; + tsf_document_map_[input_type].text_store = text_store; + tsf_document_map_[input_type].document_manager = document_manager; + tsf_document_map_[input_type].cookie = cookie; + } + return true; +} + +bool TSFBridgeImpl::InitializeDisabledContext(ITfContext* context) { + Microsoft::WRL::ComPtr<ITfCompartmentMgr> compartment_mgr; + if (FAILED(context->QueryInterface(IID_PPV_ARGS(&compartment_mgr)))) { + DVLOG(1) << "Failed to get CompartmentMgr."; + return false; + } + + Microsoft::WRL::ComPtr<ITfCompartment> disabled_compartment; + if (FAILED(compartment_mgr->GetCompartment( + GUID_COMPARTMENT_KEYBOARD_DISABLED, + disabled_compartment.GetAddressOf()))) { + DVLOG(1) << "Failed to get keyboard disabled compartment."; + return false; + } + + base::win::ScopedVariant variant; + variant.Set(1); + if (FAILED(disabled_compartment->SetValue(client_id_, variant.ptr()))) { + DVLOG(1) << "Failed to disable the DocumentMgr."; + return false; + } + + Microsoft::WRL::ComPtr<ITfCompartment> empty_context; + if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT, + empty_context.GetAddressOf()))) { + DVLOG(1) << "Failed to get empty context compartment."; + return false; + } + base::win::ScopedVariant empty_context_variant; + empty_context_variant.Set(static_cast<int32_t>(1)); + if (FAILED( + empty_context->SetValue(client_id_, empty_context_variant.ptr()))) { + DVLOG(1) << "Failed to set empty context."; + return false; + } + + return true; +} + +bool TSFBridgeImpl::IsFocused(ITfDocumentMgr* document_manager) { + Microsoft::WRL::ComPtr<ITfDocumentMgr> focused_document_manager; + if (FAILED( + thread_manager_->GetFocus(focused_document_manager.GetAddressOf()))) + return false; + return focused_document_manager.Get() == document_manager; +} + +bool TSFBridgeImpl::IsInitialized() { + return client_id_ != TF_CLIENTID_NULL; +} + +void TSFBridgeImpl::UpdateAssociateFocus() { + if (attached_window_handle_ == nullptr) + return; + TSFDocument* document = GetAssociatedDocument(); + if (document == nullptr) { + ClearAssociateFocus(); + return; + } + // NOTE: ITfThreadMgr::AssociateFocus does not increment the ref count of + // the document manager to be attached. It is our responsibility to make sure + // the attached document manager will not be destroyed while it is attached. + // This should be true as long as TSFBridge::Shutdown() is called late phase + // of UI thread shutdown. + Microsoft::WRL::ComPtr<ITfDocumentMgr> previous_focus; + thread_manager_->AssociateFocus(attached_window_handle_, + document->document_manager.Get(), + previous_focus.GetAddressOf()); +} + +void TSFBridgeImpl::ClearAssociateFocus() { + if (attached_window_handle_ == nullptr) + return; + Microsoft::WRL::ComPtr<ITfDocumentMgr> previous_focus; + thread_manager_->AssociateFocus(attached_window_handle_, nullptr, + previous_focus.GetAddressOf()); +} + +TSFBridgeImpl::TSFDocument* TSFBridgeImpl::GetAssociatedDocument() { + if (!client_) + return nullptr; + TSFDocumentMap::iterator it = + tsf_document_map_.find(client_->GetTextInputType()); + if (it == tsf_document_map_.end()) { + it = tsf_document_map_.find(TEXT_INPUT_TYPE_TEXT); + // This check is necessary because it's possible that we failed to + // initialize |tsf_document_map_| and it has no TEXT_INPUT_TYPE_TEXT. + if (it == tsf_document_map_.end()) + return nullptr; + } + return &it->second; +} + +void Finalize(void* data) { + TSFBridgeImpl* delegate = static_cast<TSFBridgeImpl*>(data); + delete delegate; +} + +base::ThreadLocalStorage::Slot& TSFBridgeTLS() { + static base::NoDestructor<base::ThreadLocalStorage::Slot> tsf_bridge_tls( + &Finalize); + return *tsf_bridge_tls; +} + +} // namespace + +// TsfBridge ----------------------------------------------------------------- + +TSFBridge::TSFBridge() {} + +TSFBridge::~TSFBridge() {} + +// static +void TSFBridge::Initialize() { + if (!base::MessageLoopForUI::IsCurrent()) { + DVLOG(1) << "Do not use TSFBridge without UI thread."; + return; + } + TSFBridgeImpl* delegate = static_cast<TSFBridgeImpl*>(TSFBridgeTLS().Get()); + if (delegate) + return; + // If we aren't supporting TSF early out. + if (!base::FeatureList::IsEnabled(features::kTSFImeSupport)) + return; + delegate = new TSFBridgeImpl(); + TSFBridgeTLS().Set(delegate); + delegate->Initialize(); +} + +// static +TSFBridge* TSFBridge::ReplaceForTesting(TSFBridge* bridge) { + if (!base::MessageLoopForUI::IsCurrent()) { + DVLOG(1) << "Do not use TSFBridge without UI thread."; + return nullptr; + } + TSFBridge* old_bridge = TSFBridge::GetInstance(); + TSFBridgeTLS().Set(bridge); + return old_bridge; +} + +// static +void TSFBridge::Shutdown() { + if (!base::MessageLoopForUI::IsCurrent()) { + DVLOG(1) << "Do not use TSFBridge without UI thread."; + } + TSFBridgeImpl* delegate = static_cast<TSFBridgeImpl*>(TSFBridgeTLS().Get()); + TSFBridgeTLS().Set(nullptr); + delete delegate; +} + +// static +TSFBridge* TSFBridge::GetInstance() { + if (!base::MessageLoopForUI::IsCurrent()) { + DVLOG(1) << "Do not use TSFBridge without UI thread."; + return nullptr; + } + TSFBridgeImpl* delegate = static_cast<TSFBridgeImpl*>(TSFBridgeTLS().Get()); + DCHECK(delegate) << "Do no call GetInstance before TSFBridge::Initialize."; + return delegate; +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/tsf_bridge.h b/chromium/ui/base/ime/win/tsf_bridge.h new file mode 100644 index 00000000000..0d24edd6fac --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_bridge.h @@ -0,0 +1,94 @@ +// Copyright 2018 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_WIN_TSF_BRIDGE_H_ +#define UI_BASE_IME_WIN_TSF_BRIDGE_H_ + +#include <msctf.h> +#include <windows.h> +#include <wrl/client.h> + +#include "base/macros.h" +#include "ui/base/ime/ui_base_ime_export.h" + +namespace ui { +class TextInputClient; + +// TSFBridge provides high level IME related operations on top of Text Services +// Framework (TSF). TSFBridge is managed by TLS because TSF related stuff is +// associated with each thread and not allowed to access across thread boundary. +// To be consistent with IMM32 behavior, TSFBridge is shared in the same thread. +// TSFBridge is used by the web content text inputting field, for example +// DisableIME() should be called if a password field is focused. +// +// TSFBridge also manages connectivity between TSFTextStore which is the backend +// of text inputting and current focused TextInputClient. +// +// All methods in this class must be used in UI thread. +class UI_BASE_IME_EXPORT TSFBridge { + public: + virtual ~TSFBridge(); + + // Returns the thread local TSFBridge instance. Initialize() must be called + // first. Do not cache this pointer and use it after TSFBridge Shutdown(). + static TSFBridge* GetInstance(); + + // Sets the thread local instance. Must be called before any calls to + // GetInstance(). + static void Initialize(); + + // Injects an alternative TSFBridge such as MockTSFBridge for testing. The + // injected object should be released by the caller. This function returns + // previous TSFBridge pointer with ownership. + static TSFBridge* ReplaceForTesting(TSFBridge* bridge); + + // Destroys the thread local instance. + static void Shutdown(); + + // Handles TextInputTypeChanged event. RWHVW is responsible for calling this + // handler whenever renderer's input text type is changed. Does nothing + // unless |client| is focused. + virtual void OnTextInputTypeChanged(const TextInputClient* client) = 0; + + // Sends an event to TSF manager that the text layout should be updated. + virtual void OnTextLayoutChanged() = 0; + + // Cancels the ongoing composition if exists. + // Returns true if there is no composition. + // Returns false if an edit session is on-going. + // Returns false if an error occures. + virtual bool CancelComposition() = 0; + + // Confirms the ongoing composition if exists. + // Returns true if there is no composition. + // Returns false if an edit session is on-going. + // Returns false if an error occures. + virtual bool ConfirmComposition() = 0; + + // Sets currently focused TextInputClient. + // Caller must free |client|. + virtual void SetFocusedClient(HWND focused_window, + TextInputClient* client) = 0; + + // Removes currently focused TextInputClient. + // Caller must free |client|. + virtual void RemoveFocusedClient(TextInputClient* client) = 0; + + // Obtains current thread manager. + virtual Microsoft::WRL::ComPtr<ITfThreadMgr> GetThreadManager() = 0; + + // Returns the focused text input client. + virtual TextInputClient* GetFocusedTextInputClient() const = 0; + + protected: + // Uses GetInstance() instead. + TSFBridge(); + + private: + DISALLOW_COPY_AND_ASSIGN(TSFBridge); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_TSF_BRIDGE_H_ diff --git a/chromium/ui/base/ime/win/tsf_event_router.cc b/chromium/ui/base/ime/win/tsf_event_router.cc new file mode 100644 index 00000000000..a67ad224f7c --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_event_router.cc @@ -0,0 +1,296 @@ +// Copyright 2018 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/win/tsf_event_router.h" + +#include <msctf.h> +#include <wrl/client.h> +#include <set> +#include <utility> + +#include "base/bind.h" +#include "ui/base/win/atl_module.h" +#include "ui/gfx/range/range.h" + +namespace ui { + +// TSFEventRouter::Delegate ------------------------------------ + +// The implementation class of ITfUIElementSink, whose member functions will be +// called back by TSF when the UI element status is changed, for example when +// the candidate window is opened or closed. This class also implements +// ITfTextEditSink, whose member function is called back by TSF when the text +// editting session is finished. +class ATL_NO_VTABLE TSFEventRouter::Delegate + : public ATL::CComObjectRootEx<CComSingleThreadModel>, + public ITfUIElementSink, + public ITfTextEditSink { + public: + BEGIN_COM_MAP(Delegate) + COM_INTERFACE_ENTRY(ITfUIElementSink) + COM_INTERFACE_ENTRY(ITfTextEditSink) + END_COM_MAP() + + Delegate(); + ~Delegate(); + + // ITfTextEditSink: + STDMETHOD(OnEndEdit) + (ITfContext* context, + TfEditCookie read_only_cookie, + ITfEditRecord* edit_record) override; + + // ITfUiElementSink: + STDMETHOD(BeginUIElement)(DWORD element_id, BOOL* is_show) override; + STDMETHOD(UpdateUIElement)(DWORD element_id) override; + STDMETHOD(EndUIElement)(DWORD element_id) override; + + // Sets |thread_manager| to be monitored. |thread_manager| can be nullptr. + void SetManager(ITfThreadMgr* thread_manager); + + // Returns true if the IME is composing text. + bool IsImeComposing(); + + // Sets |router| to be forwarded TSF-related events. + void SetRouter(TSFEventRouter* router); + + private: + // Returns current composition range. Returns gfx::Range::InvalidRange if + // there is no composition. + static gfx::Range GetCompositionRange(ITfContext* context); + + // Returns true if the given |element_id| represents the candidate window. + bool IsCandidateWindowInternal(DWORD element_id); + + // A context associated with this class. + Microsoft::WRL::ComPtr<ITfContext> context_; + + // The ITfSource associated with |context_|. + Microsoft::WRL::ComPtr<ITfSource> context_source_; + + // The cookie for |context_source_|. + DWORD context_source_cookie_; + + // A UIElementMgr associated with this class. + Microsoft::WRL::ComPtr<ITfUIElementMgr> ui_element_manager_; + + // The ITfSouce associated with |ui_element_manager_|. + Microsoft::WRL::ComPtr<ITfSource> ui_source_; + + // The set of currently opened candidate window ids. + std::set<DWORD> open_candidate_window_ids_; + + // The cookie for |ui_source_|. + DWORD ui_source_cookie_ = TF_INVALID_COOKIE; + + TSFEventRouter* router_ = nullptr; + gfx::Range previous_composition_range_; + + DISALLOW_COPY_AND_ASSIGN(Delegate); +}; + +TSFEventRouter::Delegate::Delegate() + : previous_composition_range_(gfx::Range::InvalidRange()) {} + +TSFEventRouter::Delegate::~Delegate() = default; + +void TSFEventRouter::Delegate::SetRouter(TSFEventRouter* router) { + router_ = router; +} + +STDMETHODIMP TSFEventRouter::Delegate::OnEndEdit(ITfContext* context, + TfEditCookie read_only_cookie, + ITfEditRecord* edit_record) { + if (!edit_record || !context) + return E_INVALIDARG; + if (!router_) + return S_OK; + + // |edit_record| can be used to obtain updated ranges in terms of text + // contents and/or text attributes. Here we are interested only in text update + // so we use TF_GTP_INCL_TEXT and check if there is any range which contains + // updated text. + Microsoft::WRL::ComPtr<IEnumTfRanges> ranges; + if (FAILED(edit_record->GetTextAndPropertyUpdates(TF_GTP_INCL_TEXT, nullptr, + 0, ranges.GetAddressOf()))) + return S_OK; // Don't care about failures. + + ULONG fetched_count = 0; + Microsoft::WRL::ComPtr<ITfRange> range; + if (FAILED(ranges->Next(1, range.GetAddressOf(), &fetched_count))) + return S_OK; // Don't care about failures. + + const gfx::Range composition_range = GetCompositionRange(context); + + if (!previous_composition_range_.IsValid() && composition_range.IsValid()) + router_->OnTSFStartComposition(); + + // |fetched_count| != 0 means there is at least one range that contains + // updated text. + if (fetched_count != 0) + router_->OnTextUpdated(composition_range); + + if (previous_composition_range_.IsValid() && !composition_range.IsValid()) + router_->OnTSFEndComposition(); + + previous_composition_range_ = composition_range; + return S_OK; +} + +STDMETHODIMP TSFEventRouter::Delegate::BeginUIElement(DWORD element_id, + BOOL* is_show) { + if (is_show) + *is_show = TRUE; // Without this the UI element will not be shown. + + if (!IsCandidateWindowInternal(element_id)) + return S_OK; + + std::pair<std::set<DWORD>::iterator, bool> insert_result = + open_candidate_window_ids_.insert(element_id); + // Don't call if |router_| is null or |element_id| is already handled. + if (router_ && insert_result.second) + router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); + + return S_OK; +} + +STDMETHODIMP TSFEventRouter::Delegate::UpdateUIElement(DWORD element_id) { + return S_OK; +} + +STDMETHODIMP TSFEventRouter::Delegate::EndUIElement(DWORD element_id) { + if ((open_candidate_window_ids_.erase(element_id) != 0) && router_) + router_->OnCandidateWindowCountChanged(open_candidate_window_ids_.size()); + return S_OK; +} + +void TSFEventRouter::Delegate::SetManager(ITfThreadMgr* thread_manager) { + context_.Reset(); + + if (context_source_) { + context_source_->UnadviseSink(context_source_cookie_); + context_source_.Reset(); + } + context_source_cookie_ = TF_INVALID_COOKIE; + + ui_element_manager_.Reset(); + if (ui_source_) { + ui_source_->UnadviseSink(ui_source_cookie_); + ui_source_.Reset(); + } + ui_source_cookie_ = TF_INVALID_COOKIE; + + if (!thread_manager) + return; + + Microsoft::WRL::ComPtr<ITfDocumentMgr> document_manager; + if (FAILED(thread_manager->GetFocus(document_manager.GetAddressOf())) || + !document_manager.Get() || + FAILED(document_manager->GetBase(context_.GetAddressOf())) || + FAILED(context_.CopyTo(context_source_.GetAddressOf()))) + return; + context_source_->AdviseSink(IID_ITfTextEditSink, + static_cast<ITfTextEditSink*>(this), + &context_source_cookie_); + + if (FAILED( + thread_manager->QueryInterface(IID_PPV_ARGS(&ui_element_manager_))) || + FAILED(ui_element_manager_.CopyTo(ui_source_.GetAddressOf()))) + return; + ui_source_->AdviseSink(IID_ITfUIElementSink, + static_cast<ITfUIElementSink*>(this), + &ui_source_cookie_); +} + +bool TSFEventRouter::Delegate::IsImeComposing() { + return context_ && GetCompositionRange(context_.Get()).IsValid(); +} + +// static +gfx::Range TSFEventRouter::Delegate::GetCompositionRange(ITfContext* context) { + DCHECK(context); + Microsoft::WRL::ComPtr<ITfContextComposition> context_composition; + if (FAILED(context->QueryInterface(IID_PPV_ARGS(&context_composition)))) + return gfx::Range::InvalidRange(); + Microsoft::WRL::ComPtr<IEnumITfCompositionView> enum_composition_view; + if (FAILED(context_composition->EnumCompositions( + enum_composition_view.GetAddressOf()))) + return gfx::Range::InvalidRange(); + Microsoft::WRL::ComPtr<ITfCompositionView> composition_view; + if (enum_composition_view->Next(1, composition_view.GetAddressOf(), + nullptr) != S_OK) + return gfx::Range::InvalidRange(); + + Microsoft::WRL::ComPtr<ITfRange> range; + if (FAILED(composition_view->GetRange(range.GetAddressOf()))) + return gfx::Range::InvalidRange(); + + Microsoft::WRL::ComPtr<ITfRangeACP> range_acp; + if (FAILED(range.CopyTo(range_acp.GetAddressOf()))) + return gfx::Range::InvalidRange(); + + LONG start = 0; + LONG length = 0; + if (FAILED(range_acp->GetExtent(&start, &length))) + return gfx::Range::InvalidRange(); + + return gfx::Range(start, start + length); +} + +bool TSFEventRouter::Delegate::IsCandidateWindowInternal(DWORD element_id) { + DCHECK(ui_element_manager_.Get()); + Microsoft::WRL::ComPtr<ITfUIElement> ui_element; + if (FAILED(ui_element_manager_->GetUIElement(element_id, + ui_element.GetAddressOf()))) + return false; + Microsoft::WRL::ComPtr<ITfCandidateListUIElement> candidate_list_ui_element; + return SUCCEEDED(ui_element.CopyTo(candidate_list_ui_element.GetAddressOf())); +} + +// TSFEventRouter ------------------------------------------------------------ + +TSFEventRouter::TSFEventRouter(TSFEventRouterObserver* observer) + : observer_(observer) { + DCHECK(observer_); + CComObject<Delegate>* delegate; + ui::win::CreateATLModuleIfNeeded(); + if (SUCCEEDED(CComObject<Delegate>::CreateInstance(&delegate))) { + delegate->AddRef(); + delegate_.Attach(delegate); + delegate_->SetRouter(this); + } +} + +TSFEventRouter::~TSFEventRouter() { + if (delegate_) { + delegate_->SetManager(nullptr); + delegate_->SetRouter(nullptr); + } +} + +bool TSFEventRouter::IsImeComposing() { + return delegate_->IsImeComposing(); +} + +void TSFEventRouter::OnCandidateWindowCountChanged(size_t window_count) { + observer_->OnCandidateWindowCountChanged(window_count); +} + +void TSFEventRouter::OnTSFStartComposition() { + observer_->OnTSFStartComposition(); +} + +void TSFEventRouter::OnTextUpdated(const gfx::Range& composition_range) { + observer_->OnTextUpdated(composition_range); +} + +void TSFEventRouter::OnTSFEndComposition() { + observer_->OnTSFEndComposition(); +} + +void TSFEventRouter::SetManager(ITfThreadMgr* thread_manager) { + delegate_->SetManager(thread_manager); +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/tsf_event_router.h b/chromium/ui/base/ime/win/tsf_event_router.h new file mode 100644 index 00000000000..d094e1ba9b1 --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_event_router.h @@ -0,0 +1,78 @@ +// Copyright 2018 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_WIN_TSF_EVENT_ROUTER_H_ +#define UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_ + +#include <atlbase.h> +#include <atlcom.h> +#include <msctf.h> + +#include <set> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "ui/base/ime/text_input_type.h" +#include "ui/base/ime/ui_base_ime_export.h" +#include "ui/gfx/range/range.h" + +namespace ui { + +class TSFEventRouterObserver { + public: + TSFEventRouterObserver() {} + + // Called when the number of currently opened candidate windows changes. + virtual void OnCandidateWindowCountChanged(size_t window_count) {} + + // Called when a composition is started. + virtual void OnTSFStartComposition() {} + + // Called when the text contents are updated. If there is no composition, + // gfx::Range::InvalidRange is passed to |composition_range|. + virtual void OnTextUpdated(const gfx::Range& composition_range) {} + + // Called when a composition is terminated. + virtual void OnTSFEndComposition() {} + + protected: + virtual ~TSFEventRouterObserver() {} + + private: + DISALLOW_COPY_AND_ASSIGN(TSFEventRouterObserver); +}; + +// This class monitors TSF related events and forwards them to given +// |observer|. +class UI_BASE_IME_EXPORT TSFEventRouter { + public: + // Do not pass NULL to |observer|. + explicit TSFEventRouter(TSFEventRouterObserver* observer); + virtual ~TSFEventRouter(); + + // Returns true if the IME is composing text. + bool IsImeComposing(); + + // Callbacks from the TSFEventRouterDelegate: + void OnCandidateWindowCountChanged(size_t window_count); + void OnTSFStartComposition(); + void OnTextUpdated(const gfx::Range& composition_range); + void OnTSFEndComposition(); + + // Sets |thread_manager| to be monitored. |thread_manager| can be NULL. + void SetManager(ITfThreadMgr* thread_manager); + + private: + class Delegate; + + CComPtr<Delegate> delegate_; + + TSFEventRouterObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(TSFEventRouter); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_TSF_EVENT_ROUTER_H_ diff --git a/chromium/ui/base/ime/win/tsf_text_store.cc b/chromium/ui/base/ime/win/tsf_text_store.cc new file mode 100644 index 00000000000..6fb766ded9e --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_text_store.cc @@ -0,0 +1,925 @@ +// Copyright 2018 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. + +#define INITGUID // required for GUID_PROP_INPUTSCOPE +#include "ui/base/ime/win/tsf_text_store.h" + +#include <InputScope.h> +#include <OleCtl.h> +#include <wrl/client.h> + +#include <algorithm> + +#include "base/win/scoped_variant.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/base/ime/win/tsf_input_scope.h" +#include "ui/gfx/geometry/rect.h" + +namespace ui { +namespace { + +// We support only one view. +const TsViewCookie kViewCookie = 1; + +} // namespace + +TSFTextStore::TSFTextStore() { + if (FAILED(::CoCreateInstance(CLSID_TF_CategoryMgr, nullptr, CLSCTX_ALL, + IID_PPV_ARGS(&category_manager_)))) { + LOG(FATAL) << "Failed to initialize CategoryMgr."; + return; + } + if (FAILED(::CoCreateInstance(CLSID_TF_DisplayAttributeMgr, nullptr, + CLSCTX_ALL, + IID_PPV_ARGS(&display_attribute_manager_)))) { + LOG(FATAL) << "Failed to initialize DisplayAttributeMgr."; + return; + } +} + +TSFTextStore::~TSFTextStore() {} + +ULONG STDMETHODCALLTYPE TSFTextStore::AddRef() { + return InterlockedIncrement(&ref_count_); +} + +ULONG STDMETHODCALLTYPE TSFTextStore::Release() { + const LONG count = InterlockedDecrement(&ref_count_); + if (!count) { + delete this; + return 0; + } + return static_cast<ULONG>(count); +} + +STDMETHODIMP TSFTextStore::QueryInterface(REFIID iid, void** result) { + if (iid == IID_IUnknown || iid == IID_ITextStoreACP) { + *result = static_cast<ITextStoreACP*>(this); + } else if (iid == IID_ITfContextOwnerCompositionSink) { + *result = static_cast<ITfContextOwnerCompositionSink*>(this); + } else if (iid == IID_ITfTextEditSink) { + *result = static_cast<ITfTextEditSink*>(this); + } else { + *result = nullptr; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +STDMETHODIMP TSFTextStore::AdviseSink(REFIID iid, + IUnknown* unknown, + DWORD mask) { + if (!IsEqualGUID(iid, IID_ITextStoreACPSink)) + return E_INVALIDARG; + if (text_store_acp_sink_) { + if (text_store_acp_sink_.Get() == unknown) { + text_store_acp_sink_mask_ = mask; + return S_OK; + } else { + return CONNECT_E_ADVISELIMIT; + } + } + if (FAILED(unknown->QueryInterface(IID_PPV_ARGS(&text_store_acp_sink_)))) + return E_UNEXPECTED; + text_store_acp_sink_mask_ = mask; + + return S_OK; +} + +STDMETHODIMP TSFTextStore::FindNextAttrTransition( + LONG acp_start, + LONG acp_halt, + ULONG num_filter_attributes, + const TS_ATTRID* filter_attributes, + DWORD flags, + LONG* acp_next, + BOOL* found, + LONG* found_offset) { + if (!acp_next || !found || !found_offset) + return E_INVALIDARG; + // We don't support any attributes. + // So we always return "not found". + *acp_next = 0; + *found = FALSE; + *found_offset = 0; + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetACPFromPoint(TsViewCookie view_cookie, + const POINT* point, + DWORD flags, + LONG* acp) { + NOTIMPLEMENTED(); + if (view_cookie != kViewCookie) + return E_INVALIDARG; + return E_NOTIMPL; +} + +STDMETHODIMP TSFTextStore::GetActiveView(TsViewCookie* view_cookie) { + if (!view_cookie) + return E_INVALIDARG; + // We support only one view. + *view_cookie = kViewCookie; + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetEmbedded(LONG acp_pos, + REFGUID service, + REFIID iid, + IUnknown** unknown) { + // We don't support any embedded objects. + NOTIMPLEMENTED(); + if (!unknown) + return E_INVALIDARG; + *unknown = nullptr; + return E_NOTIMPL; +} + +STDMETHODIMP TSFTextStore::GetEndACP(LONG* acp) { + if (!acp) + return E_INVALIDARG; + if (!HasReadLock()) + return TS_E_NOLOCK; + *acp = string_buffer_.size(); + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetFormattedText(LONG acp_start, + LONG acp_end, + IDataObject** data_object) { + NOTIMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP TSFTextStore::GetScreenExt(TsViewCookie view_cookie, RECT* rect) { + if (view_cookie != kViewCookie) + return E_INVALIDARG; + if (!rect) + return E_INVALIDARG; + + // {0, 0, 0, 0} means that the document rect is not currently displayed. + SetRect(rect, 0, 0, 0, 0); + + if (!IsWindow(window_handle_)) + return E_FAIL; + + // Currently ui::TextInputClient does not expose the document rect. So use + // the Win32 client rectangle instead. + // TODO(yukawa): Upgrade TextInputClient so that the client can retrieve the + // document rectangle. + RECT client_rect = {}; + if (!GetClientRect(window_handle_, &client_rect)) + return E_FAIL; + POINT left_top = {client_rect.left, client_rect.top}; + POINT right_bottom = {client_rect.right, client_rect.bottom}; + if (!ClientToScreen(window_handle_, &left_top)) + return E_FAIL; + if (!ClientToScreen(window_handle_, &right_bottom)) + return E_FAIL; + + rect->left = left_top.x; + rect->top = left_top.y; + rect->right = right_bottom.x; + rect->bottom = right_bottom.y; + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetSelection(ULONG selection_index, + ULONG selection_buffer_size, + TS_SELECTION_ACP* selection_buffer, + ULONG* fetched_count) { + if (!selection_buffer) + return E_INVALIDARG; + if (!fetched_count) + return E_INVALIDARG; + if (!HasReadLock()) + return TS_E_NOLOCK; + *fetched_count = 0; + if ((selection_buffer_size > 0) && + ((selection_index == 0) || (selection_index == TS_DEFAULT_SELECTION))) { + selection_buffer[0].acpStart = selection_.start(); + selection_buffer[0].acpEnd = selection_.end(); + selection_buffer[0].style.ase = TS_AE_END; + selection_buffer[0].style.fInterimChar = FALSE; + *fetched_count = 1; + } + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetStatus(TS_STATUS* status) { + if (!status) + return E_INVALIDARG; + + status->dwDynamicFlags = 0; + // We use transitory contexts and we don't support hidden text. + // TODO(dtapuska): Remove TS_SS_TRANSITORY it was added to fix + // https://crbug.com/148355 + status->dwStaticFlags = TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT; + + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetText(LONG acp_start, + LONG acp_end, + wchar_t* text_buffer, + ULONG text_buffer_size, + ULONG* text_buffer_copied, + TS_RUNINFO* run_info_buffer, + ULONG run_info_buffer_size, + ULONG* run_info_buffer_copied, + LONG* next_acp) { + if (!text_buffer_copied || !run_info_buffer_copied) + return E_INVALIDARG; + if (!text_buffer && text_buffer_size != 0) + return E_INVALIDARG; + if (!run_info_buffer && run_info_buffer_size != 0) + return E_INVALIDARG; + if (!next_acp) + return E_INVALIDARG; + if (!HasReadLock()) + return TF_E_NOLOCK; + const LONG string_buffer_size = string_buffer_.size(); + if (acp_end == -1) + acp_end = string_buffer_size; + if (!((0 <= acp_start) && (acp_start <= acp_end) && + (acp_end <= string_buffer_size))) { + return TF_E_INVALIDPOS; + } + acp_end = std::min(acp_end, acp_start + static_cast<LONG>(text_buffer_size)); + *text_buffer_copied = acp_end - acp_start; + + const base::string16& result = + string_buffer_.substr(acp_start, *text_buffer_copied); + for (size_t i = 0; i < result.size(); ++i) { + text_buffer[i] = result[i]; + } + + if (run_info_buffer_size) { + run_info_buffer[0].uCount = *text_buffer_copied; + run_info_buffer[0].type = TS_RT_PLAIN; + *run_info_buffer_copied = 1; + } + + *next_acp = acp_end; + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetTextExt(TsViewCookie view_cookie, + LONG acp_start, + LONG acp_end, + RECT* rect, + BOOL* clipped) { + if (!rect || !clipped) + return E_INVALIDARG; + if (!text_input_client_) + return E_UNEXPECTED; + if (view_cookie != kViewCookie) + return E_INVALIDARG; + if (!HasReadLock()) + return TS_E_NOLOCK; + if (!((static_cast<LONG>(committed_size_) <= acp_start) && + (acp_start <= acp_end) && + (acp_end <= static_cast<LONG>(string_buffer_.size())))) { + return TS_E_INVALIDPOS; + } + + // According to a behavior of notepad.exe and wordpad.exe, top left corner of + // rect indicates a first character's one, and bottom right corner of rect + // indicates a last character's one. + // We use RECT instead of gfx::Rect since left position may be bigger than + // right position when composition has multiple lines. + RECT result; + gfx::Rect tmp_rect; + const uint32_t start_pos = acp_start - committed_size_; + const uint32_t end_pos = acp_end - committed_size_; + + if (start_pos == end_pos) { + // According to MSDN document, if |acp_start| and |acp_end| are equal it is + // OK to just return E_INVALIDARG. + // http://msdn.microsoft.com/en-us/library/ms538435 + // But when using Pinin IME of Windows 8, this method is called with the + // equal values of |acp_start| and |acp_end|. So we handle this condition. + if (start_pos == 0) { + if (text_input_client_->GetCompositionCharacterBounds(0, &tmp_rect)) { + tmp_rect.set_width(0); + result = tmp_rect.ToRECT(); + } else if (string_buffer_.size() == committed_size_) { + result = text_input_client_->GetCaretBounds().ToRECT(); + } else { + return TS_E_NOLAYOUT; + } + } else if (text_input_client_->GetCompositionCharacterBounds(start_pos - 1, + &tmp_rect)) { + result.left = tmp_rect.right(); + result.right = tmp_rect.right(); + result.top = tmp_rect.y(); + result.bottom = tmp_rect.bottom(); + } else { + return TS_E_NOLAYOUT; + } + } else { + if (text_input_client_->GetCompositionCharacterBounds(start_pos, + &tmp_rect)) { + result.left = tmp_rect.x(); + result.top = tmp_rect.y(); + result.right = tmp_rect.right(); + result.bottom = tmp_rect.bottom(); + if (text_input_client_->GetCompositionCharacterBounds(end_pos - 1, + &tmp_rect)) { + result.right = tmp_rect.right(); + result.bottom = tmp_rect.bottom(); + } else { + // We may not be able to get the last character bounds, so we use the + // first character bounds instead of returning TS_E_NOLAYOUT. + } + } else { + // Hack for PPAPI flash. PPAPI flash does not support GetCaretBounds, so + // it's better to return previous caret rectangle instead. + // TODO(nona, kinaba): Remove this hack. + if (start_pos == 0) { + result = text_input_client_->GetCaretBounds().ToRECT(); + } else { + return TS_E_NOLAYOUT; + } + } + } + + *rect = result; + *clipped = FALSE; + return S_OK; +} + +STDMETHODIMP TSFTextStore::GetWnd(TsViewCookie view_cookie, + HWND* window_handle) { + if (!window_handle) + return E_INVALIDARG; + if (view_cookie != kViewCookie) + return E_INVALIDARG; + *window_handle = window_handle_; + return S_OK; +} + +STDMETHODIMP TSFTextStore::InsertEmbedded(DWORD flags, + LONG acp_start, + LONG acp_end, + IDataObject* data_object, + TS_TEXTCHANGE* change) { + // We don't support any embedded objects. + NOTIMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP TSFTextStore::InsertEmbeddedAtSelection(DWORD flags, + IDataObject* data_object, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* change) { + // We don't support any embedded objects. + NOTIMPLEMENTED(); + return E_NOTIMPL; +} + +STDMETHODIMP TSFTextStore::InsertTextAtSelection(DWORD flags, + const wchar_t* text_buffer, + ULONG text_buffer_size, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* text_change) { + const LONG start_pos = selection_.start(); + const LONG end_pos = selection_.end(); + const LONG new_end_pos = start_pos + text_buffer_size; + + if (flags & TS_IAS_QUERYONLY) { + if (!HasReadLock()) + return TS_E_NOLOCK; + if (acp_start) + *acp_start = start_pos; + if (acp_end) { + *acp_end = end_pos; + } + return S_OK; + } + + if (!HasReadWriteLock()) + return TS_E_NOLOCK; + if (!text_buffer) + return E_INVALIDARG; + + DCHECK_LE(start_pos, end_pos); + string_buffer_ = string_buffer_.substr(0, start_pos) + + base::string16(text_buffer, text_buffer + text_buffer_size) + + string_buffer_.substr(end_pos); + if (acp_start) + *acp_start = start_pos; + if (acp_end) + *acp_end = new_end_pos; + if (text_change) { + text_change->acpStart = start_pos; + text_change->acpOldEnd = end_pos; + text_change->acpNewEnd = new_end_pos; + } + selection_.set_start(start_pos); + selection_.set_end(new_end_pos); + return S_OK; +} + +STDMETHODIMP TSFTextStore::QueryInsert(LONG acp_test_start, + LONG acp_test_end, + ULONG text_size, + LONG* acp_result_start, + LONG* acp_result_end) { + if (!acp_result_start || !acp_result_end || acp_test_start > acp_test_end) + return E_INVALIDARG; + const LONG committed_size = static_cast<LONG>(committed_size_); + const LONG buffer_size = static_cast<LONG>(string_buffer_.size()); + *acp_result_start = + std::min(std::max(committed_size, acp_test_start), buffer_size); + *acp_result_end = + std::min(std::max(committed_size, acp_test_end), buffer_size); + return S_OK; +} + +STDMETHODIMP TSFTextStore::QueryInsertEmbedded(const GUID* service, + const FORMATETC* format, + BOOL* insertable) { + if (!format) + return E_INVALIDARG; + // We don't support any embedded objects. + if (insertable) + *insertable = FALSE; + return S_OK; +} + +STDMETHODIMP TSFTextStore::RequestAttrsAtPosition( + LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) { + // We don't support any document attributes. + // This method just returns S_OK, and the subsequently called + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. + return S_OK; +} + +STDMETHODIMP TSFTextStore::RequestAttrsTransitioningAtPosition( + LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) { + // We don't support any document attributes. + // This method just returns S_OK, and the subsequently called + // RetrieveRequestedAttrs() returns 0 as the number of supported attributes. + return S_OK; +} + +STDMETHODIMP TSFTextStore::RequestLock(DWORD lock_flags, HRESULT* result) { + if (!text_store_acp_sink_.Get()) + return E_FAIL; + if (!result) + return E_INVALIDARG; + + if (current_lock_type_ != 0) { + if (lock_flags & TS_LF_SYNC) { + // Can't lock synchronously. + *result = TS_E_SYNCHRONOUS; + return S_OK; + } + // Queue the lock request. + lock_queue_.push_back(lock_flags & TS_LF_READWRITE); + *result = TS_S_ASYNC; + return S_OK; + } + + // Lock + current_lock_type_ = (lock_flags & TS_LF_READWRITE); + + edit_flag_ = false; + const size_t last_committed_size = committed_size_; + + // Grant the lock. + *result = text_store_acp_sink_->OnLockGranted(current_lock_type_); + + // Unlock + current_lock_type_ = 0; + + // Handles the pending lock requests. + while (!lock_queue_.empty()) { + current_lock_type_ = lock_queue_.front(); + lock_queue_.pop_front(); + text_store_acp_sink_->OnLockGranted(current_lock_type_); + current_lock_type_ = 0; + } + + if (!edit_flag_) { + return S_OK; + } + + // If the text store is edited in OnLockGranted(), we may need to call + // TextInputClient::InsertText() or TextInputClient::SetCompositionText(). + const size_t new_committed_size = committed_size_; + const base::string16& new_committed_string = string_buffer_.substr( + last_committed_size, new_committed_size - last_committed_size); + const base::string16& composition_string = + string_buffer_.substr(new_committed_size); + + // If there is new committed string, calls TextInputClient::InsertText(). + if ((!new_committed_string.empty()) && text_input_client_) { + text_input_client_->InsertText(new_committed_string); + } + + // Calls TextInputClient::SetCompositionText(). + CompositionText composition_text; + composition_text.text = composition_string; + composition_text.ime_text_spans = text_spans_; + // Adjusts the offset. + for (size_t i = 0; i < composition_text.ime_text_spans.size(); ++i) { + composition_text.ime_text_spans[i].start_offset -= new_committed_size; + composition_text.ime_text_spans[i].end_offset -= new_committed_size; + } + if (selection_.start() < new_committed_size) { + composition_text.selection.set_start(0); + } else { + composition_text.selection.set_start(selection_.start() - + new_committed_size); + } + if (selection_.end() < new_committed_size) { + composition_text.selection.set_end(0); + } else { + composition_text.selection.set_end(selection_.end() - new_committed_size); + } + if (text_input_client_) + text_input_client_->SetCompositionText(composition_text); + + // If there is no composition string, clear the text store status. + // And call OnSelectionChange(), OnLayoutChange(), and OnTextChange(). + if ((composition_string.empty()) && (new_committed_size != 0)) { + string_buffer_.clear(); + committed_size_ = 0; + selection_.set_start(0); + selection_.set_end(0); + if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) + text_store_acp_sink_->OnSelectionChange(); + if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); + if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { + TS_TEXTCHANGE textChange; + textChange.acpStart = 0; + textChange.acpOldEnd = new_committed_size; + textChange.acpNewEnd = 0; + text_store_acp_sink_->OnTextChange(0, &textChange); + } + } + + return S_OK; +} + +STDMETHODIMP TSFTextStore::RequestSupportedAttrs( + DWORD /* flags */, // Seems that we should ignore this. + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer) { + if (!attribute_buffer) + return E_INVALIDARG; + if (!text_input_client_) + return E_FAIL; + // We support only input scope attribute. + for (size_t i = 0; i < attribute_buffer_size; ++i) { + if (IsEqualGUID(GUID_PROP_INPUTSCOPE, attribute_buffer[i])) + return S_OK; + } + return E_FAIL; +} + +STDMETHODIMP TSFTextStore::RetrieveRequestedAttrs( + ULONG attribute_buffer_size, + TS_ATTRVAL* attribute_buffer, + ULONG* attribute_buffer_copied) { + if (!attribute_buffer_copied) + return E_INVALIDARG; + if (!attribute_buffer) + return E_INVALIDARG; + if (!text_input_client_) + return E_UNEXPECTED; + // We support only input scope attribute. + *attribute_buffer_copied = 0; + if (attribute_buffer_size == 0) + return S_OK; + + attribute_buffer[0].dwOverlapId = 0; + attribute_buffer[0].idAttr = GUID_PROP_INPUTSCOPE; + attribute_buffer[0].varValue.vt = VT_UNKNOWN; + attribute_buffer[0].varValue.punkVal = + tsf_inputscope::CreateInputScope(text_input_client_->GetTextInputType(), + text_input_client_->GetTextInputMode()); + attribute_buffer[0].varValue.punkVal->AddRef(); + *attribute_buffer_copied = 1; + return S_OK; +} + +STDMETHODIMP TSFTextStore::SetSelection( + ULONG selection_buffer_size, + const TS_SELECTION_ACP* selection_buffer) { + if (!HasReadWriteLock()) + return TF_E_NOLOCK; + if (selection_buffer_size > 0) { + const LONG start_pos = selection_buffer[0].acpStart; + const LONG end_pos = selection_buffer[0].acpEnd; + if (!((static_cast<LONG>(committed_size_) <= start_pos) && + (start_pos <= end_pos) && + (end_pos <= static_cast<LONG>(string_buffer_.size())))) { + return TF_E_INVALIDPOS; + } + selection_.set_start(start_pos); + selection_.set_end(end_pos); + } + return S_OK; +} + +STDMETHODIMP TSFTextStore::SetText(DWORD flags, + LONG acp_start, + LONG acp_end, + const wchar_t* text_buffer, + ULONG text_buffer_size, + TS_TEXTCHANGE* text_change) { + if (!HasReadWriteLock()) + return TS_E_NOLOCK; + if (!((static_cast<LONG>(committed_size_) <= acp_start) && + (acp_start <= acp_end) && + (acp_end <= static_cast<LONG>(string_buffer_.size())))) { + return TS_E_INVALIDPOS; + } + + TS_SELECTION_ACP selection; + selection.acpStart = acp_start; + selection.acpEnd = acp_end; + selection.style.ase = TS_AE_NONE; + selection.style.fInterimChar = 0; + + HRESULT ret; + ret = SetSelection(1, &selection); + if (ret != S_OK) + return ret; + + TS_TEXTCHANGE change; + ret = InsertTextAtSelection(0, text_buffer, text_buffer_size, &acp_start, + &acp_end, &change); + if (ret != S_OK) + return ret; + + if (text_change) + *text_change = change; + + return S_OK; +} + +STDMETHODIMP TSFTextStore::UnadviseSink(IUnknown* unknown) { + if (text_store_acp_sink_.Get() != unknown) + return CONNECT_E_NOCONNECTION; + text_store_acp_sink_.Reset(); + text_store_acp_sink_mask_ = 0; + return S_OK; +} + +STDMETHODIMP TSFTextStore::OnStartComposition( + ITfCompositionView* composition_view, + BOOL* ok) { + if (ok) + *ok = TRUE; + return S_OK; +} + +STDMETHODIMP TSFTextStore::OnUpdateComposition( + ITfCompositionView* composition_view, + ITfRange* range) { + return S_OK; +} + +STDMETHODIMP TSFTextStore::OnEndComposition( + ITfCompositionView* composition_view) { + return S_OK; +} + +STDMETHODIMP TSFTextStore::OnEndEdit(ITfContext* context, + TfEditCookie read_only_edit_cookie, + ITfEditRecord* edit_record) { + if (!context || !edit_record) + return E_INVALIDARG; + + size_t committed_size; + ImeTextSpans spans; + if (!GetCompositionStatus(context, read_only_edit_cookie, &committed_size, + &spans)) { + return S_OK; + } + text_spans_ = spans; + committed_size_ = committed_size; + edit_flag_ = true; + return S_OK; +} + +bool TSFTextStore::GetDisplayAttribute(TfGuidAtom guid_atom, + TF_DISPLAYATTRIBUTE* attribute) { + GUID guid; + if (FAILED(category_manager_->GetGUID(guid_atom, &guid))) + return false; + + Microsoft::WRL::ComPtr<ITfDisplayAttributeInfo> display_attribute_info; + if (FAILED(display_attribute_manager_->GetDisplayAttributeInfo( + guid, display_attribute_info.GetAddressOf(), nullptr))) { + return false; + } + return SUCCEEDED(display_attribute_info->GetAttributeInfo(attribute)); +} + +bool TSFTextStore::GetCompositionStatus( + ITfContext* context, + const TfEditCookie read_only_edit_cookie, + size_t* committed_size, + ImeTextSpans* spans) { + DCHECK(context); + DCHECK(committed_size); + DCHECK(spans); + const GUID* rgGuids[2] = {&GUID_PROP_COMPOSING, &GUID_PROP_ATTRIBUTE}; + Microsoft::WRL::ComPtr<ITfReadOnlyProperty> track_property; + if (FAILED(context->TrackProperties(rgGuids, 2, nullptr, 0, + track_property.GetAddressOf()))) { + return false; + } + + *committed_size = 0; + spans->clear(); + Microsoft::WRL::ComPtr<ITfRange> start_to_end_range; + Microsoft::WRL::ComPtr<ITfRange> end_range; + if (FAILED(context->GetStart(read_only_edit_cookie, + start_to_end_range.GetAddressOf()))) { + return false; + } + if (FAILED(context->GetEnd(read_only_edit_cookie, end_range.GetAddressOf()))) + return false; + if (FAILED(start_to_end_range->ShiftEndToRange( + read_only_edit_cookie, end_range.Get(), TF_ANCHOR_END))) { + return false; + } + + Microsoft::WRL::ComPtr<IEnumTfRanges> ranges; + if (FAILED(track_property->EnumRanges(read_only_edit_cookie, + ranges.GetAddressOf(), + start_to_end_range.Get()))) { + return false; + } + + while (true) { + Microsoft::WRL::ComPtr<ITfRange> range; + if (ranges->Next(1, range.GetAddressOf(), nullptr) != S_OK) + break; + base::win::ScopedVariant value; + Microsoft::WRL::ComPtr<IEnumTfPropertyValue> enum_prop_value; + if (FAILED(track_property->GetValue(read_only_edit_cookie, range.Get(), + value.Receive()))) { + return false; + } + if (FAILED(value.AsInput()->punkVal->QueryInterface( + IID_PPV_ARGS(&enum_prop_value)))) + return false; + + TF_PROPERTYVAL property_value; + bool is_composition = false; + bool has_display_attribute = false; + TF_DISPLAYATTRIBUTE display_attribute = {}; + while (enum_prop_value->Next(1, &property_value, nullptr) == S_OK) { + if (IsEqualGUID(property_value.guidId, GUID_PROP_COMPOSING)) { + is_composition = (property_value.varValue.lVal == TRUE); + } else if (IsEqualGUID(property_value.guidId, GUID_PROP_ATTRIBUTE)) { + TfGuidAtom guid_atom = + static_cast<TfGuidAtom>(property_value.varValue.lVal); + if (GetDisplayAttribute(guid_atom, &display_attribute)) + has_display_attribute = true; + } + VariantClear(&property_value.varValue); + } + + Microsoft::WRL::ComPtr<ITfRangeACP> range_acp; + range.CopyTo(range_acp.GetAddressOf()); + LONG start_pos, length; + range_acp->GetExtent(&start_pos, &length); + if (!is_composition) { + if (*committed_size < static_cast<size_t>(start_pos + length)) + *committed_size = start_pos + length; + } else { + ImeTextSpan span; + span.start_offset = start_pos; + span.end_offset = start_pos + length; + span.underline_color = SK_ColorBLACK; + span.background_color = SK_ColorTRANSPARENT; + if (has_display_attribute) { + span.thickness = display_attribute.fBoldLine + ? ImeTextSpan::Thickness::kThick + : ImeTextSpan::Thickness::kThin; + } + spans->push_back(span); + } + } + return true; +} + +void TSFTextStore::SetFocusedTextInputClient( + HWND focused_window, + TextInputClient* text_input_client) { + window_handle_ = focused_window; + text_input_client_ = text_input_client; +} + +void TSFTextStore::RemoveFocusedTextInputClient( + TextInputClient* text_input_client) { + if (text_input_client_ == text_input_client) { + window_handle_ = nullptr; + text_input_client_ = nullptr; + } +} + +bool TSFTextStore::CancelComposition() { + // If there is an on-going document lock, we must not edit the text. + if (edit_flag_) + return false; + + if (string_buffer_.empty()) + return true; + + // Unlike ImmNotifyIME(NI_COMPOSITIONSTR, CPS_CANCEL, 0) in IMM32, TSF does + // not have a dedicated method to cancel composition. However, CUAS actually + // has a protocol conversion from CPS_CANCEL into TSF operations. According + // to the observations on Windows 7, TIPs are expected to cancel composition + // when an on-going composition text is replaced with an empty string. So + // we use the same operation to cancel composition here to minimize the risk + // of potential compatibility issues. + + const size_t previous_buffer_size = string_buffer_.size(); + string_buffer_.clear(); + committed_size_ = 0; + selection_.set_start(0); + selection_.set_end(0); + if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) + text_store_acp_sink_->OnSelectionChange(); + if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); + if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { + TS_TEXTCHANGE textChange = {}; + textChange.acpStart = 0; + textChange.acpOldEnd = previous_buffer_size; + textChange.acpNewEnd = 0; + text_store_acp_sink_->OnTextChange(0, &textChange); + } + return true; +} + +bool TSFTextStore::ConfirmComposition() { + // If there is an on-going document lock, we must not edit the text. + if (edit_flag_) + return false; + + if (string_buffer_.empty()) + return true; + + // See the comment in TSFTextStore::CancelComposition. + // This logic is based on the observation about how to emulate + // ImmNotifyIME(NI_COMPOSITIONSTR, CPS_COMPLETE, 0) by CUAS. + + const base::string16& composition_text = + string_buffer_.substr(committed_size_); + if (!composition_text.empty()) + text_input_client_->InsertText(composition_text); + + const size_t previous_buffer_size = string_buffer_.size(); + string_buffer_.clear(); + committed_size_ = 0; + selection_.set_start(0); + selection_.set_end(0); + if (text_store_acp_sink_mask_ & TS_AS_SEL_CHANGE) + text_store_acp_sink_->OnSelectionChange(); + if (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); + if (text_store_acp_sink_mask_ & TS_AS_TEXT_CHANGE) { + TS_TEXTCHANGE textChange = {}; + textChange.acpStart = 0; + textChange.acpOldEnd = previous_buffer_size; + textChange.acpNewEnd = 0; + text_store_acp_sink_->OnTextChange(0, &textChange); + } + return true; +} + +void TSFTextStore::SendOnLayoutChange() { + if (text_store_acp_sink_ && (text_store_acp_sink_mask_ & TS_AS_LAYOUT_CHANGE)) + text_store_acp_sink_->OnLayoutChange(TS_LC_CHANGE, 0); +} + +bool TSFTextStore::HasReadLock() const { + return (current_lock_type_ & TS_LF_READ) == TS_LF_READ; +} + +bool TSFTextStore::HasReadWriteLock() const { + return (current_lock_type_ & TS_LF_READWRITE) == TS_LF_READWRITE; +} + +} // namespace ui diff --git a/chromium/ui/base/ime/win/tsf_text_store.h b/chromium/ui/base/ime/win/tsf_text_store.h new file mode 100644 index 00000000000..687ca8f6d62 --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_text_store.h @@ -0,0 +1,311 @@ +// Copyright 2018 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_WIN_TSF_TEXT_STORE_H_ +#define UI_BASE_IME_WIN_TSF_TEXT_STORE_H_ + +#include <msctf.h> +#include <wrl/client.h> +#include <deque> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/strings/string16.h" +#include "ui/base/ime/ime_text_span.h" +#include "ui/base/ime/ui_base_ime_export.h" +#include "ui/gfx/range/range.h" + +namespace ui { +class TextInputClient; + +// TSFTextStore is used to interact with the input method via TSF manager. +// TSFTextStore have a string buffer which is manipulated by TSF manager through +// ITextStoreACP interface methods such as SetText(). +// When the input method updates the composition, TSFTextStore calls +// TextInputClient::SetCompositionText(). And when the input method finishes the +// composition, TSFTextStore calls TextInputClient::InsertText() and clears the +// buffer. +// +// How TSFTextStore works: +// - The user enters "a". +// - The input method set composition as "a". +// - TSF manager calls TSFTextStore::RequestLock(). +// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted(). +// - In OnLockGranted(), TSF manager calls +// - TSFTextStore::OnStartComposition() +// - TSFTextStore::SetText() +// The string buffer is set as "a". +// - TSFTextStore::OnUpdateComposition() +// - TSFTextStore::OnEndEdit() +// TSFTextStore can get the composition information such as underlines. +// - TSFTextStore calls TextInputClient::SetCompositionText(). +// "a" is shown with an underline as composition string. +// - The user enters <space>. +// - The input method set composition as "A". +// - TSF manager calls TSFTextStore::RequestLock(). +// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted(). +// - In OnLockGranted(), TSF manager calls +// - TSFTextStore::SetText() +// The string buffer is set as "A". +// - TSFTextStore::OnUpdateComposition() +// - TSFTextStore::OnEndEdit() +// - TSFTextStore calls TextInputClient::SetCompositionText(). +// "A" is shown with an underline as composition string. +// - The user enters <enter>. +// - The input method commits "A". +// - TSF manager calls TSFTextStore::RequestLock(). +// - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted(). +// - In OnLockGranted(), TSF manager calls +// - TSFTextStore::OnEndComposition() +// - TSFTextStore::OnEndEdit() +// TSFTextStore knows "A" is committed. +// - TSFTextStore calls TextInputClient::InsertText(). +// "A" is shown as committed string. +// - TSFTextStore clears the string buffer. +// - TSFTextStore calls OnSelectionChange(), OnLayoutChange() and +// OnTextChange() of ITextStoreACPSink to let TSF manager know that the +// string buffer has been changed. +// +// About the locking scheme: +// When TSF manager manipulates the string buffer it calls RequestLock() to get +// the lock of the document. If TSFTextStore can grant the lock request, it +// callbacks ITextStoreACPSink::OnLockGranted(). +// RequestLock() is called from only one thread, but called recursively in +// OnLockGranted() or OnSelectionChange() or OnLayoutChange() or OnTextChange(). +// If the document is locked and the lock request is asynchronous, TSFTextStore +// queues the request. The queued requests will be handled after the current +// lock is removed. +// More information about document locks can be found here: +// http://msdn.microsoft.com/en-us/library/ms538064 +// +// More information about TSF can be found here: +// http://msdn.microsoft.com/en-us/library/ms629032 +class UI_BASE_IME_EXPORT TSFTextStore : public ITextStoreACP, + public ITfContextOwnerCompositionSink, + public ITfTextEditSink { + public: + TSFTextStore(); + virtual ~TSFTextStore(); + + // ITextStoreACP: + STDMETHOD_(ULONG, AddRef)() override; + STDMETHOD_(ULONG, Release)() override; + STDMETHOD(QueryInterface)(REFIID iid, void** ppv) override; + STDMETHOD(AdviseSink)(REFIID iid, IUnknown* unknown, DWORD mask) override; + STDMETHOD(FindNextAttrTransition) + (LONG acp_start, + LONG acp_halt, + ULONG num_filter_attributes, + const TS_ATTRID* filter_attributes, + DWORD flags, + LONG* acp_next, + BOOL* found, + LONG* found_offset) override; + STDMETHOD(GetACPFromPoint) + (TsViewCookie view_cookie, + const POINT* point, + DWORD flags, + LONG* acp) override; + STDMETHOD(GetActiveView)(TsViewCookie* view_cookie) override; + STDMETHOD(GetEmbedded) + (LONG acp_pos, REFGUID service, REFIID iid, IUnknown** unknown) override; + STDMETHOD(GetEndACP)(LONG* acp) override; + STDMETHOD(GetFormattedText) + (LONG acp_start, LONG acp_end, IDataObject** data_object) override; + STDMETHOD(GetScreenExt)(TsViewCookie view_cookie, RECT* rect) override; + STDMETHOD(GetSelection) + (ULONG selection_index, + ULONG selection_buffer_size, + TS_SELECTION_ACP* selection_buffer, + ULONG* fetched_count) override; + STDMETHOD(GetStatus)(TS_STATUS* pdcs) override; + STDMETHOD(GetText) + (LONG acp_start, + LONG acp_end, + wchar_t* text_buffer, + ULONG text_buffer_size, + ULONG* text_buffer_copied, + TS_RUNINFO* run_info_buffer, + ULONG run_info_buffer_size, + ULONG* run_info_buffer_copied, + LONG* next_acp) override; + STDMETHOD(GetTextExt) + (TsViewCookie view_cookie, + LONG acp_start, + LONG acp_end, + RECT* rect, + BOOL* clipped) override; + STDMETHOD(GetWnd)(TsViewCookie view_cookie, HWND* window_handle) override; + STDMETHOD(InsertEmbedded) + (DWORD flags, + LONG acp_start, + LONG acp_end, + IDataObject* data_object, + TS_TEXTCHANGE* change) override; + STDMETHOD(InsertEmbeddedAtSelection) + (DWORD flags, + IDataObject* data_object, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* change) override; + STDMETHOD(InsertTextAtSelection) + (DWORD flags, + const wchar_t* text_buffer, + ULONG text_buffer_size, + LONG* acp_start, + LONG* acp_end, + TS_TEXTCHANGE* text_change) override; + STDMETHOD(QueryInsert) + (LONG acp_test_start, + LONG acp_test_end, + ULONG text_size, + LONG* acp_result_start, + LONG* acp_result_end) override; + STDMETHOD(QueryInsertEmbedded) + (const GUID* service, const FORMATETC* format, BOOL* insertable) override; + STDMETHOD(RequestAttrsAtPosition) + (LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) override; + STDMETHOD(RequestAttrsTransitioningAtPosition) + (LONG acp_pos, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer, + DWORD flags) override; + STDMETHOD(RequestLock)(DWORD lock_flags, HRESULT* result) override; + STDMETHOD(RequestSupportedAttrs) + (DWORD flags, + ULONG attribute_buffer_size, + const TS_ATTRID* attribute_buffer) override; + STDMETHOD(RetrieveRequestedAttrs) + (ULONG attribute_buffer_size, + TS_ATTRVAL* attribute_buffer, + ULONG* attribute_buffer_copied) override; + STDMETHOD(SetSelection) + (ULONG selection_buffer_size, + const TS_SELECTION_ACP* selection_buffer) override; + STDMETHOD(SetText) + (DWORD flags, + LONG acp_start, + LONG acp_end, + const wchar_t* text_buffer, + ULONG text_buffer_size, + TS_TEXTCHANGE* text_change) override; + STDMETHOD(UnadviseSink)(IUnknown* unknown) override; + + // ITfContextOwnerCompositionSink: + STDMETHOD(OnStartComposition) + (ITfCompositionView* composition_view, BOOL* ok) override; + STDMETHOD(OnUpdateComposition) + (ITfCompositionView* composition_view, ITfRange* range) override; + STDMETHOD(OnEndComposition)(ITfCompositionView* composition_view) override; + + // ITfTextEditSink: + STDMETHOD(OnEndEdit) + (ITfContext* context, + TfEditCookie read_only_edit_cookie, + ITfEditRecord* edit_record) override; + + // Sets currently focused TextInputClient. + void SetFocusedTextInputClient(HWND focused_window, + TextInputClient* text_input_client); + // Removes currently focused TextInputClient. + void RemoveFocusedTextInputClient(TextInputClient* text_input_client); + + // Cancels the ongoing composition if exists. + bool CancelComposition(); + + // Confirms the ongoing composition if exists. + bool ConfirmComposition(); + + // Sends OnLayoutChange() via |text_store_acp_sink_|. + void SendOnLayoutChange(); + + private: + friend class TSFTextStoreTest; + friend class TSFTextStoreTestCallback; + + // Checks if the document has a read-only lock. + bool HasReadLock() const; + + // Checks if the document has a read and write lock. + bool HasReadWriteLock() const; + + // Gets the display attribute structure. + bool GetDisplayAttribute(TfGuidAtom guid_atom, + TF_DISPLAYATTRIBUTE* attribute); + + // Gets the committed string size and underline information of the context. + bool GetCompositionStatus(ITfContext* context, + const TfEditCookie read_only_edit_cookie, + size_t* committed_size, + ImeTextSpans* spans); + + // The refrence count of this instance. + volatile LONG ref_count_ = 0; + + // A pointer of ITextStoreACPSink, this instance is given in AdviseSink. + Microsoft::WRL::ComPtr<ITextStoreACPSink> text_store_acp_sink_; + + // The current mask of |text_store_acp_sink_|. + DWORD text_store_acp_sink_mask_ = 0; + + // HWND of the current view window which is set in SetFocusedTextInputClient. + HWND window_handle_ = nullptr; + + // Current TextInputClient which is set in SetFocusedTextInputClient. + TextInputClient* text_input_client_ = nullptr; + + // TODO(dtapuska): determine if we can expose more the entire document + // more than the committed string and composition string to the TIP. + // |string_buffer_| contains committed string and composition string. + // Example: "aoi" is committed, and "umi" is under composition. + // |string_buffer_|: "aoiumi" + // |committed_size_|: 3 + base::string16 string_buffer_; + size_t committed_size_ = 0; + + // |selection_start_| and |selection_end_| indicates the selection range. + // Example: "iue" is selected + // |string_buffer_|: "aiueo" + // |selection_.start()|: 1 + // |selection_.end()|: 4 + gfx::Range selection_; + + // |start_offset| and |end_offset| of |text_spans_| indicates + // the offsets in |string_buffer_|. + // Example: "aoi" is committed. There are two underlines in "umi" and "no". + // |string_buffer_|: "aoiumino" + // |committed_size_|: 3 + // text_spans_[0].start_offset: 3 + // text_spans_[0].end_offset: 6 + // text_spans_[1].start_offset: 6 + // text_spans_[1].end_offset: 8 + ImeTextSpans text_spans_; + + // |edit_flag_| indicates that the status is edited during + // ITextStoreACPSink::OnLockGranted(). + bool edit_flag_ = false; + + // The type of current lock. + // 0: No lock. + // TS_LF_READ: read-only lock. + // TS_LF_READWRITE: read/write lock. + DWORD current_lock_type_ = 0; + + // Queue of the lock request used in RequestLock(). + std::deque<DWORD> lock_queue_; + + // Category manager and Display attribute manager are used to obtain the + // attributes of the composition string. + Microsoft::WRL::ComPtr<ITfCategoryMgr> category_manager_; + Microsoft::WRL::ComPtr<ITfDisplayAttributeMgr> display_attribute_manager_; + + DISALLOW_COPY_AND_ASSIGN(TSFTextStore); +}; + +} // namespace ui + +#endif // UI_BASE_IME_WIN_TSF_TEXT_STORE_H_ diff --git a/chromium/ui/base/ime/win/tsf_text_store_unittest.cc b/chromium/ui/base/ime/win/tsf_text_store_unittest.cc new file mode 100644 index 00000000000..76c3eb149d9 --- /dev/null +++ b/chromium/ui/base/ime/win/tsf_text_store_unittest.cc @@ -0,0 +1,1302 @@ +// Copyright 2018 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/win/tsf_text_store.h" + +#include <initguid.h> // for GUID_NULL and GUID_PROP_INPUTSCOPE + +#include <InputScope.h> +#include <OleCtl.h> +#include <wrl/client.h> + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/win/scoped_com_initializer.h" +#include "base/win/scoped_variant.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/ime/text_input_client.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/rect.h" + +using testing::_; +using testing::Invoke; +using testing::Return; + +namespace ui { +namespace { + +class MockTextInputClient : public TextInputClient { + public: + ~MockTextInputClient() {} + MOCK_METHOD1(SetCompositionText, void(const ui::CompositionText&)); + MOCK_METHOD0(ConfirmCompositionText, void()); + MOCK_METHOD0(ClearCompositionText, void()); + MOCK_METHOD1(InsertText, void(const base::string16&)); + MOCK_METHOD1(InsertChar, void(const ui::KeyEvent&)); + MOCK_CONST_METHOD0(GetTextInputType, ui::TextInputType()); + MOCK_CONST_METHOD0(GetTextInputMode, ui::TextInputMode()); + MOCK_CONST_METHOD0(GetTextDirection, base::i18n::TextDirection()); + MOCK_CONST_METHOD0(GetTextInputFlags, int()); + MOCK_CONST_METHOD0(CanComposeInline, bool()); + MOCK_CONST_METHOD0(GetCaretBounds, gfx::Rect()); + MOCK_CONST_METHOD2(GetCompositionCharacterBounds, bool(uint32_t, gfx::Rect*)); + MOCK_CONST_METHOD0(HasCompositionText, bool()); + MOCK_CONST_METHOD1(GetTextRange, bool(gfx::Range*)); + MOCK_CONST_METHOD1(GetCompositionTextRange, bool(gfx::Range*)); + MOCK_CONST_METHOD1(GetSelectionRange, bool(gfx::Range*)); + MOCK_METHOD1(SetSelectionRange, bool(const gfx::Range&)); + MOCK_METHOD1(DeleteRange, bool(const gfx::Range&)); + MOCK_CONST_METHOD2(GetTextFromRange, + bool(const gfx::Range&, base::string16*)); + MOCK_METHOD0(OnInputMethodChanged, void()); + MOCK_METHOD1(ChangeTextDirectionAndLayoutAlignment, + bool(base::i18n::TextDirection)); + MOCK_METHOD2(ExtendSelectionAndDelete, void(size_t, size_t)); + MOCK_METHOD1(EnsureCaretNotInRect, void(const gfx::Rect&)); + MOCK_CONST_METHOD1(IsTextEditCommandEnabled, bool(TextEditCommand)); + MOCK_METHOD1(SetTextEditCommandForNextKeyEvent, void(TextEditCommand)); + MOCK_CONST_METHOD0(GetClientSourceInfo, const std::string&()); +}; + +class MockStoreACPSink : public ITextStoreACPSink { + public: + MockStoreACPSink() : ref_count_(0) {} + + // IUnknown + ULONG STDMETHODCALLTYPE AddRef() override { + return InterlockedIncrement(&ref_count_); + } + ULONG STDMETHODCALLTYPE Release() override { + const LONG count = InterlockedDecrement(&ref_count_); + if (!count) { + delete this; + return 0; + } + return static_cast<ULONG>(count); + } + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void** report) override { + if (iid == IID_IUnknown || iid == IID_ITextStoreACPSink) { + *report = static_cast<ITextStoreACPSink*>(this); + } else { + *report = nullptr; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; + } + + // ITextStoreACPSink + MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE, + OnTextChange, + HRESULT(DWORD, const TS_TEXTCHANGE*)); + MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, OnSelectionChange, HRESULT()); + MOCK_METHOD2_WITH_CALLTYPE(STDMETHODCALLTYPE, + OnLayoutChange, + HRESULT(TsLayoutCode, TsViewCookie)); + MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnStatusChange, HRESULT(DWORD)); + MOCK_METHOD4_WITH_CALLTYPE(STDMETHODCALLTYPE, + OnAttrsChange, + HRESULT(LONG, LONG, ULONG, const TS_ATTRID*)); + MOCK_METHOD1_WITH_CALLTYPE(STDMETHODCALLTYPE, OnLockGranted, HRESULT(DWORD)); + MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, + OnStartEditTransaction, + HRESULT()); + MOCK_METHOD0_WITH_CALLTYPE(STDMETHODCALLTYPE, + OnEndEditTransaction, + HRESULT()); + + private: + virtual ~MockStoreACPSink() {} + + volatile LONG ref_count_; +}; + +const HWND kWindowHandle = reinterpret_cast<HWND>(1); + +} // namespace + +class TSFTextStoreTest : public testing::Test { + protected: + void SetUp() override { + text_store_ = new TSFTextStore(); + sink_ = new MockStoreACPSink(); + EXPECT_EQ(S_OK, text_store_->AdviseSink(IID_ITextStoreACPSink, sink_.get(), + TS_AS_ALL_SINKS)); + text_store_->SetFocusedTextInputClient(kWindowHandle, &text_input_client_); + } + + void TearDown() override { + EXPECT_EQ(S_OK, text_store_->UnadviseSink(sink_.get())); + sink_ = nullptr; + text_store_ = nullptr; + } + + // Accessors to the internal state of TSFTextStore. + base::string16* string_buffer() { return &text_store_->string_buffer_; } + size_t* committed_size() { return &text_store_->committed_size_; } + + base::win::ScopedCOMInitializer com_initializer_; + MockTextInputClient text_input_client_; + scoped_refptr<TSFTextStore> text_store_; + scoped_refptr<MockStoreACPSink> sink_; +}; + +class TSFTextStoreTestCallback { + public: + explicit TSFTextStoreTestCallback(TSFTextStore* text_store) + : text_store_(text_store) { + CHECK(text_store_); + } + virtual ~TSFTextStoreTestCallback() {} + + protected: + // Accessors to the internal state of TSFTextStore. + bool* edit_flag() { return &text_store_->edit_flag_; } + base::string16* string_buffer() { return &text_store_->string_buffer_; } + size_t* committed_size() { return &text_store_->committed_size_; } + gfx::Range* selection() { return &text_store_->selection_; } + ImeTextSpans* text_spans() { return &text_store_->text_spans_; } + + void SetInternalState(const base::string16& new_string_buffer, + LONG new_committed_size, + LONG new_selection_start, + LONG new_selection_end) { + ASSERT_LE(0, new_committed_size); + ASSERT_LE(new_committed_size, new_selection_start); + ASSERT_LE(new_selection_start, new_selection_end); + ASSERT_LE(new_selection_end, static_cast<LONG>(new_string_buffer.size())); + *string_buffer() = new_string_buffer; + *committed_size() = new_committed_size; + selection()->set_start(new_selection_start); + selection()->set_end(new_selection_end); + } + + bool HasReadLock() const { return text_store_->HasReadLock(); } + bool HasReadWriteLock() const { return text_store_->HasReadWriteLock(); } + + void GetSelectionTest(LONG expected_acp_start, LONG expected_acp_end) { + TS_SELECTION_ACP selection = {}; + ULONG fetched = 0; + EXPECT_EQ(S_OK, text_store_->GetSelection(0, 1, &selection, &fetched)); + EXPECT_EQ(1u, fetched); + EXPECT_EQ(expected_acp_start, selection.acpStart); + EXPECT_EQ(expected_acp_end, selection.acpEnd); + } + + void SetSelectionTest(LONG acp_start, LONG acp_end, HRESULT expected_result) { + TS_SELECTION_ACP selection = {}; + selection.acpStart = acp_start; + selection.acpEnd = acp_end; + selection.style.ase = TS_AE_NONE; + selection.style.fInterimChar = 0; + EXPECT_EQ(expected_result, text_store_->SetSelection(1, &selection)); + if (expected_result == S_OK) { + GetSelectionTest(acp_start, acp_end); + } + } + + void SetTextTest(LONG acp_start, + LONG acp_end, + const base::string16& text, + HRESULT error_code) { + TS_TEXTCHANGE change = {}; + ASSERT_EQ(error_code, + text_store_->SetText(0, acp_start, acp_end, text.c_str(), + text.size(), &change)); + if (error_code == S_OK) { + EXPECT_EQ(acp_start, change.acpStart); + EXPECT_EQ(acp_end, change.acpOldEnd); + EXPECT_EQ(acp_start + text.size(), (size_t)change.acpNewEnd); + } + } + + void GetTextTest(LONG acp_start, + LONG acp_end, + const base::string16& expected_string, + LONG expected_next_acp) { + wchar_t buffer[1024] = {}; + ULONG text_buffer_copied = 0; + TS_RUNINFO run_info = {}; + ULONG run_info_buffer_copied = 0; + LONG next_acp = 0; + ASSERT_EQ(S_OK, text_store_->GetText(acp_start, acp_end, buffer, 1024, + &text_buffer_copied, &run_info, 1, + &run_info_buffer_copied, &next_acp)); + ASSERT_EQ(expected_string.size(), text_buffer_copied); + EXPECT_EQ(expected_string, + base::string16(buffer, buffer + text_buffer_copied)); + EXPECT_EQ(1u, run_info_buffer_copied); + EXPECT_EQ(expected_string.size(), run_info.uCount); + EXPECT_EQ(TS_RT_PLAIN, run_info.type); + EXPECT_EQ(expected_next_acp, next_acp); + } + + void GetTextErrorTest(LONG acp_start, LONG acp_end, HRESULT error_code) { + wchar_t buffer[1024] = {}; + ULONG text_buffer_copied = 0; + TS_RUNINFO run_info = {}; + ULONG run_info_buffer_copied = 0; + LONG next_acp = 0; + EXPECT_EQ(error_code, + text_store_->GetText(acp_start, acp_end, buffer, 1024, + &text_buffer_copied, &run_info, 1, + &run_info_buffer_copied, &next_acp)); + } + + void InsertTextAtSelectionTest(const wchar_t* buffer, + ULONG buffer_size, + LONG expected_start, + LONG expected_end, + LONG expected_change_start, + LONG expected_change_old_end, + LONG expected_change_new_end) { + LONG start = 0; + LONG end = 0; + TS_TEXTCHANGE change = {}; + EXPECT_EQ(S_OK, text_store_->InsertTextAtSelection(0, buffer, buffer_size, + &start, &end, &change)); + EXPECT_EQ(expected_start, start); + EXPECT_EQ(expected_end, end); + EXPECT_EQ(expected_change_start, change.acpStart); + EXPECT_EQ(expected_change_old_end, change.acpOldEnd); + EXPECT_EQ(expected_change_new_end, change.acpNewEnd); + } + + void InsertTextAtSelectionQueryOnlyTest(const wchar_t* buffer, + ULONG buffer_size, + LONG expected_start, + LONG expected_end) { + LONG start = 0; + LONG end = 0; + EXPECT_EQ(S_OK, text_store_->InsertTextAtSelection(TS_IAS_QUERYONLY, buffer, + buffer_size, &start, + &end, nullptr)); + EXPECT_EQ(expected_start, start); + EXPECT_EQ(expected_end, end); + } + + void GetTextExtTest(TsViewCookie view_cookie, + LONG acp_start, + LONG acp_end, + LONG expected_left, + LONG expected_top, + LONG expected_right, + LONG expected_bottom) { + RECT rect = {}; + BOOL clipped = FALSE; + EXPECT_EQ(S_OK, text_store_->GetTextExt(view_cookie, acp_start, acp_end, + &rect, &clipped)); + EXPECT_EQ(expected_left, rect.left); + EXPECT_EQ(expected_top, rect.top); + EXPECT_EQ(expected_right, rect.right); + EXPECT_EQ(expected_bottom, rect.bottom); + EXPECT_EQ(FALSE, clipped); + } + + void GetTextExtNoLayoutTest(TsViewCookie view_cookie, + LONG acp_start, + LONG acp_end) { + RECT rect = {}; + BOOL clipped = FALSE; + EXPECT_EQ(TS_E_NOLAYOUT, text_store_->GetTextExt(view_cookie, acp_start, + acp_end, &rect, &clipped)); + } + + scoped_refptr<TSFTextStore> text_store_; + + private: + DISALLOW_COPY_AND_ASSIGN(TSFTextStoreTestCallback); +}; + +namespace { + +const HRESULT kInvalidResult = 0x12345678; + +TEST_F(TSFTextStoreTest, GetStatusTest) { + TS_STATUS status = {}; + EXPECT_EQ(S_OK, text_store_->GetStatus(&status)); + EXPECT_EQ(0u, status.dwDynamicFlags); + EXPECT_EQ((ULONG)(TS_SS_TRANSITORY | TS_SS_NOHIDDENTEXT), + status.dwStaticFlags); +} + +TEST_F(TSFTextStoreTest, QueryInsertTest) { + LONG result_start = 0; + LONG result_end = 0; + *string_buffer() = L""; + *committed_size() = 0; + EXPECT_EQ(E_INVALIDARG, + text_store_->QueryInsert(0, 0, 0, nullptr, &result_end)); + EXPECT_EQ(E_INVALIDARG, + text_store_->QueryInsert(0, 0, 0, &result_start, nullptr)); + EXPECT_EQ(S_OK, + text_store_->QueryInsert(0, 0, 0, &result_start, &result_end)); + EXPECT_EQ(0, result_start); + EXPECT_EQ(0, result_end); + *string_buffer() = L"1234"; + *committed_size() = 1; + EXPECT_EQ(S_OK, + text_store_->QueryInsert(0, 1, 0, &result_start, &result_end)); + EXPECT_EQ(1, result_start); + EXPECT_EQ(1, result_end); + EXPECT_EQ(E_INVALIDARG, + text_store_->QueryInsert(1, 0, 0, &result_start, &result_end)); + EXPECT_EQ(S_OK, + text_store_->QueryInsert(2, 2, 0, &result_start, &result_end)); + EXPECT_EQ(2, result_start); + EXPECT_EQ(2, result_end); + EXPECT_EQ(S_OK, + text_store_->QueryInsert(2, 3, 0, &result_start, &result_end)); + EXPECT_EQ(2, result_start); + EXPECT_EQ(3, result_end); + EXPECT_EQ(E_INVALIDARG, + text_store_->QueryInsert(3, 2, 0, &result_start, &result_end)); + EXPECT_EQ(S_OK, + text_store_->QueryInsert(3, 4, 0, &result_start, &result_end)); + EXPECT_EQ(3, result_start); + EXPECT_EQ(4, result_end); + EXPECT_EQ(S_OK, + text_store_->QueryInsert(3, 5, 0, &result_start, &result_end)); + EXPECT_EQ(3, result_start); + EXPECT_EQ(4, result_end); +} + +class SyncRequestLockTestCallback : public TSFTextStoreTestCallback { + public: + explicit SyncRequestLockTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store) {} + + HRESULT LockGranted1(DWORD flags) { + EXPECT_TRUE(HasReadLock()); + EXPECT_FALSE(HasReadWriteLock()); + return S_OK; + } + + HRESULT LockGranted2(DWORD flags) { + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + return S_OK; + } + + HRESULT LockGranted3(DWORD flags) { + EXPECT_TRUE(HasReadLock()); + EXPECT_FALSE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); + EXPECT_EQ(TS_E_SYNCHRONOUS, result); + return S_OK; + } + + HRESULT LockGranted4(DWORD flags) { + EXPECT_TRUE(HasReadLock()); + EXPECT_FALSE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, + text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); + EXPECT_EQ(TS_E_SYNCHRONOUS, result); + return S_OK; + } + + HRESULT LockGranted5(DWORD flags) { + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); + EXPECT_EQ(TS_E_SYNCHRONOUS, result); + return S_OK; + } + + HRESULT LockGranted6(DWORD flags) { + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, + text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); + EXPECT_EQ(TS_E_SYNCHRONOUS, result); + return S_OK; + } + + private: + DISALLOW_COPY_AND_ASSIGN(SyncRequestLockTestCallback); +}; + +TEST_F(TSFTextStoreTest, SynchronousRequestLockTest) { + SyncRequestLockTestCallback callback(text_store_.get()); + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted1)) + .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted2)) + .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted3)) + .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted4)) + .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted5)) + .WillOnce(Invoke(&callback, &SyncRequestLockTestCallback::LockGranted6)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); + EXPECT_EQ(S_OK, result); + result = kInvalidResult; + EXPECT_EQ(S_OK, + text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); + EXPECT_EQ(S_OK, result); + + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); + EXPECT_EQ(S_OK, result); + result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ | TS_LF_SYNC, &result)); + EXPECT_EQ(S_OK, result); + + result = kInvalidResult; + EXPECT_EQ(S_OK, + text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); + EXPECT_EQ(S_OK, result); + result = kInvalidResult; + EXPECT_EQ(S_OK, + text_store_->RequestLock(TS_LF_READWRITE | TS_LF_SYNC, &result)); + EXPECT_EQ(S_OK, result); +} + +class AsyncRequestLockTestCallback : public TSFTextStoreTestCallback { + public: + explicit AsyncRequestLockTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store), state_(0) {} + + HRESULT LockGranted1(DWORD flags) { + EXPECT_EQ(0, state_); + state_ = 1; + EXPECT_TRUE(HasReadLock()); + EXPECT_FALSE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(TS_S_ASYNC, result); + EXPECT_EQ(1, state_); + state_ = 2; + return S_OK; + } + + HRESULT LockGranted2(DWORD flags) { + EXPECT_EQ(2, state_); + EXPECT_TRUE(HasReadLock()); + EXPECT_FALSE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(TS_S_ASYNC, result); + EXPECT_EQ(2, state_); + state_ = 3; + return S_OK; + } + + HRESULT LockGranted3(DWORD flags) { + EXPECT_EQ(3, state_); + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(TS_S_ASYNC, result); + EXPECT_EQ(3, state_); + state_ = 4; + return S_OK; + } + + HRESULT LockGranted4(DWORD flags) { + EXPECT_EQ(4, state_); + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(TS_S_ASYNC, result); + EXPECT_EQ(4, state_); + state_ = 5; + return S_OK; + } + + HRESULT LockGranted5(DWORD flags) { + EXPECT_EQ(5, state_); + EXPECT_TRUE(HasReadLock()); + EXPECT_FALSE(HasReadWriteLock()); + state_ = 6; + return S_OK; + } + + private: + int state_; + + DISALLOW_COPY_AND_ASSIGN(AsyncRequestLockTestCallback); +}; + +TEST_F(TSFTextStoreTest, AsynchronousRequestLockTest) { + AsyncRequestLockTestCallback callback(text_store_.get()); + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted1)) + .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted2)) + .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted3)) + .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted4)) + .WillOnce(Invoke(&callback, &AsyncRequestLockTestCallback::LockGranted5)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(S_OK, result); +} + +class RequestLockTextChangeTestCallback : public TSFTextStoreTestCallback { + public: + explicit RequestLockTextChangeTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store), state_(0) {} + + HRESULT LockGranted1(DWORD flags) { + EXPECT_EQ(0, state_); + state_ = 1; + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + + *edit_flag() = true; + SetInternalState(L"012345", 6, 6, 6); + text_spans()->clear(); + + state_ = 2; + return S_OK; + } + + void InsertText(const base::string16& text) { + EXPECT_EQ(2, state_); + EXPECT_EQ(L"012345", text); + state_ = 3; + } + + void SetCompositionText(const ui::CompositionText& composition) { + EXPECT_EQ(3, state_); + EXPECT_EQ(L"", composition.text); + EXPECT_EQ(0u, composition.selection.start()); + EXPECT_EQ(0u, composition.selection.end()); + EXPECT_EQ(0u, composition.ime_text_spans.size()); + state_ = 4; + } + + HRESULT OnTextChange(DWORD flags, const TS_TEXTCHANGE* change) { + EXPECT_EQ(4, state_); + HRESULT result = kInvalidResult; + state_ = 5; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(S_OK, result); + EXPECT_EQ(6, state_); + state_ = 7; + return S_OK; + } + + HRESULT LockGranted2(DWORD flags) { + EXPECT_EQ(5, state_); + EXPECT_TRUE(HasReadLock()); + EXPECT_TRUE(HasReadWriteLock()); + state_ = 6; + return S_OK; + } + + private: + int state_; + + DISALLOW_COPY_AND_ASSIGN(RequestLockTextChangeTestCallback); +}; + +TEST_F(TSFTextStoreTest, RequestLockOnTextChangeTest) { + RequestLockTextChangeTestCallback callback(text_store_.get()); + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce( + Invoke(&callback, &RequestLockTextChangeTestCallback::LockGranted1)) + .WillOnce( + Invoke(&callback, &RequestLockTextChangeTestCallback::LockGranted2)); + + EXPECT_CALL(*sink_, OnSelectionChange()).WillOnce(Return(S_OK)); + EXPECT_CALL(*sink_, OnLayoutChange(_, _)).WillOnce(Return(S_OK)); + EXPECT_CALL(*sink_, OnTextChange(_, _)) + .WillOnce( + Invoke(&callback, &RequestLockTextChangeTestCallback::OnTextChange)); + EXPECT_CALL(text_input_client_, InsertText(_)) + .WillOnce( + Invoke(&callback, &RequestLockTextChangeTestCallback::InsertText)); + EXPECT_CALL(text_input_client_, SetCompositionText(_)) + .WillOnce(Invoke(&callback, + &RequestLockTextChangeTestCallback::SetCompositionText)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(S_OK, result); +} + +class SelectionTestCallback : public TSFTextStoreTestCallback { + public: + explicit SelectionTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store) {} + + HRESULT ReadLockGranted(DWORD flags) { + SetInternalState(L"", 0, 0, 0); + + GetSelectionTest(0, 0); + SetSelectionTest(0, 0, TF_E_NOLOCK); + + SetInternalState(L"012345", 0, 0, 3); + + GetSelectionTest(0, 3); + SetSelectionTest(0, 0, TF_E_NOLOCK); + + return S_OK; + } + + HRESULT ReadWriteLockGranted(DWORD flags) { + SetInternalState(L"", 0, 0, 0); + + SetSelectionTest(0, 0, S_OK); + GetSelectionTest(0, 0); + SetSelectionTest(0, 1, TF_E_INVALIDPOS); + SetSelectionTest(1, 0, TF_E_INVALIDPOS); + SetSelectionTest(1, 1, TF_E_INVALIDPOS); + + SetInternalState(L"0123456", 3, 3, 3); + + SetSelectionTest(0, 0, TF_E_INVALIDPOS); + SetSelectionTest(0, 1, TF_E_INVALIDPOS); + SetSelectionTest(0, 3, TF_E_INVALIDPOS); + SetSelectionTest(0, 6, TF_E_INVALIDPOS); + SetSelectionTest(0, 7, TF_E_INVALIDPOS); + SetSelectionTest(0, 8, TF_E_INVALIDPOS); + + SetSelectionTest(1, 0, TF_E_INVALIDPOS); + SetSelectionTest(1, 1, TF_E_INVALIDPOS); + SetSelectionTest(1, 3, TF_E_INVALIDPOS); + SetSelectionTest(1, 6, TF_E_INVALIDPOS); + SetSelectionTest(1, 7, TF_E_INVALIDPOS); + SetSelectionTest(1, 8, TF_E_INVALIDPOS); + + SetSelectionTest(3, 0, TF_E_INVALIDPOS); + SetSelectionTest(3, 1, TF_E_INVALIDPOS); + SetSelectionTest(3, 3, S_OK); + SetSelectionTest(3, 6, S_OK); + SetSelectionTest(3, 7, S_OK); + SetSelectionTest(3, 8, TF_E_INVALIDPOS); + + SetSelectionTest(6, 0, TF_E_INVALIDPOS); + SetSelectionTest(6, 1, TF_E_INVALIDPOS); + SetSelectionTest(6, 3, TF_E_INVALIDPOS); + SetSelectionTest(6, 6, S_OK); + SetSelectionTest(6, 7, S_OK); + SetSelectionTest(6, 8, TF_E_INVALIDPOS); + + SetSelectionTest(7, 0, TF_E_INVALIDPOS); + SetSelectionTest(7, 1, TF_E_INVALIDPOS); + SetSelectionTest(7, 3, TF_E_INVALIDPOS); + SetSelectionTest(7, 6, TF_E_INVALIDPOS); + SetSelectionTest(7, 7, S_OK); + SetSelectionTest(7, 8, TF_E_INVALIDPOS); + + SetSelectionTest(8, 0, TF_E_INVALIDPOS); + SetSelectionTest(8, 1, TF_E_INVALIDPOS); + SetSelectionTest(8, 3, TF_E_INVALIDPOS); + SetSelectionTest(8, 6, TF_E_INVALIDPOS); + SetSelectionTest(8, 7, TF_E_INVALIDPOS); + SetSelectionTest(8, 8, TF_E_INVALIDPOS); + + return S_OK; + } +}; + +TEST_F(TSFTextStoreTest, SetGetSelectionTest) { + SelectionTestCallback callback(text_store_.get()); + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, &SelectionTestCallback::ReadLockGranted)) + .WillOnce( + Invoke(&callback, &SelectionTestCallback::ReadWriteLockGranted)); + + TS_SELECTION_ACP selection_buffer = {}; + ULONG fetched_count = 0; + EXPECT_EQ(TS_E_NOLOCK, + text_store_->GetSelection(0, 1, &selection_buffer, &fetched_count)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); +} + +class SetGetTextTestCallback : public TSFTextStoreTestCallback { + public: + explicit SetGetTextTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store) {} + + HRESULT ReadLockGranted(DWORD flags) { + SetTextTest(0, 0, L"", TF_E_NOLOCK); + + GetTextTest(0, -1, L"", 0); + GetTextTest(0, 0, L"", 0); + GetTextErrorTest(0, 1, TF_E_INVALIDPOS); + + SetInternalState(L"0123456", 3, 3, 3); + + GetTextErrorTest(-1, -1, TF_E_INVALIDPOS); + GetTextErrorTest(-1, 0, TF_E_INVALIDPOS); + GetTextErrorTest(-1, 1, TF_E_INVALIDPOS); + GetTextErrorTest(-1, 3, TF_E_INVALIDPOS); + GetTextErrorTest(-1, 6, TF_E_INVALIDPOS); + GetTextErrorTest(-1, 7, TF_E_INVALIDPOS); + GetTextErrorTest(-1, 8, TF_E_INVALIDPOS); + + GetTextTest(0, -1, L"0123456", 7); + GetTextTest(0, 0, L"", 0); + GetTextTest(0, 1, L"0", 1); + GetTextTest(0, 3, L"012", 3); + GetTextTest(0, 6, L"012345", 6); + GetTextTest(0, 7, L"0123456", 7); + GetTextErrorTest(0, 8, TF_E_INVALIDPOS); + + GetTextTest(1, -1, L"123456", 7); + GetTextErrorTest(1, 0, TF_E_INVALIDPOS); + GetTextTest(1, 1, L"", 1); + GetTextTest(1, 3, L"12", 3); + GetTextTest(1, 6, L"12345", 6); + GetTextTest(1, 7, L"123456", 7); + GetTextErrorTest(1, 8, TF_E_INVALIDPOS); + + GetTextTest(3, -1, L"3456", 7); + GetTextErrorTest(3, 0, TF_E_INVALIDPOS); + GetTextErrorTest(3, 1, TF_E_INVALIDPOS); + GetTextTest(3, 3, L"", 3); + GetTextTest(3, 6, L"345", 6); + GetTextTest(3, 7, L"3456", 7); + GetTextErrorTest(3, 8, TF_E_INVALIDPOS); + + GetTextTest(6, -1, L"6", 7); + GetTextErrorTest(6, 0, TF_E_INVALIDPOS); + GetTextErrorTest(6, 1, TF_E_INVALIDPOS); + GetTextErrorTest(6, 3, TF_E_INVALIDPOS); + GetTextTest(6, 6, L"", 6); + GetTextTest(6, 7, L"6", 7); + GetTextErrorTest(6, 8, TF_E_INVALIDPOS); + + GetTextTest(7, -1, L"", 7); + GetTextErrorTest(7, 0, TF_E_INVALIDPOS); + GetTextErrorTest(7, 1, TF_E_INVALIDPOS); + GetTextErrorTest(7, 3, TF_E_INVALIDPOS); + GetTextErrorTest(7, 6, TF_E_INVALIDPOS); + GetTextTest(7, 7, L"", 7); + GetTextErrorTest(7, 8, TF_E_INVALIDPOS); + + GetTextErrorTest(8, -1, TF_E_INVALIDPOS); + GetTextErrorTest(8, 0, TF_E_INVALIDPOS); + GetTextErrorTest(8, 1, TF_E_INVALIDPOS); + GetTextErrorTest(8, 3, TF_E_INVALIDPOS); + GetTextErrorTest(8, 6, TF_E_INVALIDPOS); + GetTextErrorTest(8, 7, TF_E_INVALIDPOS); + GetTextErrorTest(8, 8, TF_E_INVALIDPOS); + + return S_OK; + } + + HRESULT ReadWriteLockGranted(DWORD flags) { + SetInternalState(L"", 0, 0, 0); + SetTextTest(0, 0, L"", S_OK); + + SetInternalState(L"", 0, 0, 0); + SetTextTest(0, 1, L"", TS_E_INVALIDPOS); + + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(0, 0, L"", TS_E_INVALIDPOS); + SetTextTest(0, 1, L"", TS_E_INVALIDPOS); + SetTextTest(0, 3, L"", TS_E_INVALIDPOS); + SetTextTest(0, 6, L"", TS_E_INVALIDPOS); + SetTextTest(0, 7, L"", TS_E_INVALIDPOS); + SetTextTest(0, 8, L"", TS_E_INVALIDPOS); + + SetTextTest(1, 0, L"", TS_E_INVALIDPOS); + SetTextTest(1, 1, L"", TS_E_INVALIDPOS); + SetTextTest(1, 3, L"", TS_E_INVALIDPOS); + SetTextTest(1, 6, L"", TS_E_INVALIDPOS); + SetTextTest(1, 7, L"", TS_E_INVALIDPOS); + SetTextTest(1, 8, L"", TS_E_INVALIDPOS); + + SetTextTest(3, 0, L"", TS_E_INVALIDPOS); + SetTextTest(3, 1, L"", TS_E_INVALIDPOS); + + SetTextTest(3, 3, L"", S_OK); + GetTextTest(0, -1, L"0123456", 7); + GetSelectionTest(3, 3); + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(3, 6, L"", S_OK); + GetTextTest(0, -1, L"0126", 4); + GetSelectionTest(3, 3); + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(3, 7, L"", S_OK); + GetTextTest(0, -1, L"012", 3); + GetSelectionTest(3, 3); + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(3, 8, L"", TS_E_INVALIDPOS); + + SetTextTest(6, 0, L"", TS_E_INVALIDPOS); + SetTextTest(6, 1, L"", TS_E_INVALIDPOS); + SetTextTest(6, 3, L"", TS_E_INVALIDPOS); + + SetTextTest(6, 6, L"", S_OK); + GetTextTest(0, -1, L"0123456", 7); + GetSelectionTest(6, 6); + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(6, 7, L"", S_OK); + GetTextTest(0, -1, L"012345", 6); + GetSelectionTest(6, 6); + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(6, 8, L"", TS_E_INVALIDPOS); + + SetTextTest(7, 0, L"", TS_E_INVALIDPOS); + SetTextTest(7, 1, L"", TS_E_INVALIDPOS); + SetTextTest(7, 3, L"", TS_E_INVALIDPOS); + SetTextTest(7, 6, L"", TS_E_INVALIDPOS); + + SetTextTest(7, 7, L"", S_OK); + GetTextTest(0, -1, L"0123456", 7); + GetSelectionTest(7, 7); + SetInternalState(L"0123456", 3, 3, 3); + + SetTextTest(7, 8, L"", TS_E_INVALIDPOS); + + SetInternalState(L"0123456", 3, 3, 3); + SetTextTest(3, 3, L"abc", S_OK); + GetTextTest(0, -1, L"012abc3456", 10); + GetSelectionTest(3, 6); + + SetInternalState(L"0123456", 3, 3, 3); + SetTextTest(3, 6, L"abc", S_OK); + GetTextTest(0, -1, L"012abc6", 7); + GetSelectionTest(3, 6); + + SetInternalState(L"0123456", 3, 3, 3); + SetTextTest(3, 7, L"abc", S_OK); + GetTextTest(0, -1, L"012abc", 6); + GetSelectionTest(3, 6); + + SetInternalState(L"0123456", 3, 3, 3); + SetTextTest(6, 6, L"abc", S_OK); + GetTextTest(0, -1, L"012345abc6", 10); + GetSelectionTest(6, 9); + + SetInternalState(L"0123456", 3, 3, 3); + SetTextTest(6, 7, L"abc", S_OK); + GetTextTest(0, -1, L"012345abc", 9); + GetSelectionTest(6, 9); + + SetInternalState(L"0123456", 3, 3, 3); + SetTextTest(7, 7, L"abc", S_OK); + GetTextTest(0, -1, L"0123456abc", 10); + GetSelectionTest(7, 10); + + return S_OK; + } + + private: + DISALLOW_COPY_AND_ASSIGN(SetGetTextTestCallback); +}; + +TEST_F(TSFTextStoreTest, SetGetTextTest) { + SetGetTextTestCallback callback(text_store_.get()); + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, &SetGetTextTestCallback::ReadLockGranted)) + .WillOnce( + Invoke(&callback, &SetGetTextTestCallback::ReadWriteLockGranted)); + + wchar_t buffer[1024] = {}; + ULONG text_buffer_copied = 0; + TS_RUNINFO run_info = {}; + ULONG run_info_buffer_copied = 0; + LONG next_acp = 0; + EXPECT_EQ(TF_E_NOLOCK, text_store_->GetText( + 0, -1, buffer, 1024, &text_buffer_copied, + &run_info, 1, &run_info_buffer_copied, &next_acp)); + TS_TEXTCHANGE change = {}; + EXPECT_EQ(TF_E_NOLOCK, text_store_->SetText(0, 0, 0, L"abc", 3, &change)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); +} + +class InsertTextAtSelectionTestCallback : public TSFTextStoreTestCallback { + public: + explicit InsertTextAtSelectionTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store) {} + + HRESULT ReadLockGranted(DWORD flags) { + const wchar_t kBuffer[] = L"0123456789"; + + SetInternalState(L"abcedfg", 0, 0, 0); + InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0); + GetSelectionTest(0, 0); + InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0); + + SetInternalState(L"abcedfg", 0, 2, 5); + InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 2, 5); + GetSelectionTest(2, 5); + InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 2, 5); + + LONG start = 0; + LONG end = 0; + TS_TEXTCHANGE change = {}; + EXPECT_EQ(TS_E_NOLOCK, text_store_->InsertTextAtSelection( + 0, kBuffer, 10, &start, &end, &change)); + return S_OK; + } + + HRESULT ReadWriteLockGranted(DWORD flags) { + SetInternalState(L"abcedfg", 0, 0, 0); + + const wchar_t kBuffer[] = L"0123456789"; + InsertTextAtSelectionQueryOnlyTest(kBuffer, 10, 0, 0); + GetSelectionTest(0, 0); + InsertTextAtSelectionQueryOnlyTest(kBuffer, 0, 0, 0); + + SetInternalState(L"", 0, 0, 0); + InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10); + GetSelectionTest(0, 10); + GetTextTest(0, -1, L"0123456789", 10); + + SetInternalState(L"abcedfg", 0, 0, 0); + InsertTextAtSelectionTest(kBuffer, 10, 0, 10, 0, 0, 10); + GetSelectionTest(0, 10); + GetTextTest(0, -1, L"0123456789abcedfg", 17); + + SetInternalState(L"abcedfg", 0, 0, 3); + InsertTextAtSelectionTest(kBuffer, 0, 0, 0, 0, 3, 0); + GetSelectionTest(0, 0); + GetTextTest(0, -1, L"edfg", 4); + + SetInternalState(L"abcedfg", 0, 3, 7); + InsertTextAtSelectionTest(kBuffer, 10, 3, 13, 3, 7, 13); + GetSelectionTest(3, 13); + GetTextTest(0, -1, L"abc0123456789", 13); + + SetInternalState(L"abcedfg", 0, 7, 7); + InsertTextAtSelectionTest(kBuffer, 10, 7, 17, 7, 7, 17); + GetSelectionTest(7, 17); + GetTextTest(0, -1, L"abcedfg0123456789", 17); + + return S_OK; + } + + private: + DISALLOW_COPY_AND_ASSIGN(InsertTextAtSelectionTestCallback); +}; + +TEST_F(TSFTextStoreTest, InsertTextAtSelectionTest) { + InsertTextAtSelectionTestCallback callback(text_store_.get()); + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, + &InsertTextAtSelectionTestCallback::ReadLockGranted)) + .WillOnce(Invoke( + &callback, &InsertTextAtSelectionTestCallback::ReadWriteLockGranted)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(S_OK, result); + result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(S_OK, result); +} + +class ScenarioTestCallback : public TSFTextStoreTestCallback { + public: + explicit ScenarioTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store) {} + + HRESULT LockGranted1(DWORD flags) { + SetSelectionTest(0, 0, S_OK); + + SetTextTest(0, 0, L"abc", S_OK); + SetTextTest(1, 2, L"xyz", S_OK); + + GetTextTest(0, -1, L"axyzc", 5); + + text_spans()->clear(); + ImeTextSpan text_span; + text_span.start_offset = 0; + text_span.end_offset = 5; + text_span.underline_color = SK_ColorBLACK; + text_span.thickness = ImeTextSpan::Thickness::kThin; + text_span.background_color = SK_ColorTRANSPARENT; + text_spans()->push_back(text_span); + *edit_flag() = true; + *committed_size() = 0; + return S_OK; + } + + void SetCompositionText1(const ui::CompositionText& composition) { + EXPECT_EQ(L"axyzc", composition.text); + EXPECT_EQ(1u, composition.selection.start()); + EXPECT_EQ(4u, composition.selection.end()); + ASSERT_EQ(1u, composition.ime_text_spans.size()); + EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color); + EXPECT_EQ(SK_ColorTRANSPARENT, + composition.ime_text_spans[0].background_color); + EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset); + EXPECT_EQ(5u, composition.ime_text_spans[0].end_offset); + EXPECT_EQ(ImeTextSpan::Thickness::kThin, + composition.ime_text_spans[0].thickness); + } + + HRESULT LockGranted2(DWORD flags) { + SetTextTest(3, 4, L"ZCP", S_OK); + GetTextTest(0, -1, L"axyZCPc", 7); + + text_spans()->clear(); + ImeTextSpan text_span; + text_span.start_offset = 3; + text_span.end_offset = 5; + text_span.underline_color = SK_ColorBLACK; + text_span.thickness = ImeTextSpan::Thickness::kThick; + text_spans()->push_back(text_span); + text_span.start_offset = 5; + text_span.end_offset = 7; + text_span.underline_color = SK_ColorBLACK; + text_span.thickness = ImeTextSpan::Thickness::kThin; + text_spans()->push_back(text_span); + + *edit_flag() = true; + *committed_size() = 3; + + return S_OK; + } + + void InsertText2(const base::string16& text) { EXPECT_EQ(L"axy", text); } + + void SetCompositionText2(const ui::CompositionText& composition) { + EXPECT_EQ(L"ZCPc", composition.text); + EXPECT_EQ(0u, composition.selection.start()); + EXPECT_EQ(3u, composition.selection.end()); + ASSERT_EQ(2u, composition.ime_text_spans.size()); + EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[0].underline_color); + EXPECT_EQ(0u, composition.ime_text_spans[0].start_offset); + EXPECT_EQ(2u, composition.ime_text_spans[0].end_offset); + EXPECT_EQ(ImeTextSpan::Thickness::kThick, + composition.ime_text_spans[0].thickness); + EXPECT_EQ(SK_ColorBLACK, composition.ime_text_spans[1].underline_color); + EXPECT_EQ(2u, composition.ime_text_spans[1].start_offset); + EXPECT_EQ(4u, composition.ime_text_spans[1].end_offset); + EXPECT_EQ(ImeTextSpan::Thickness::kThin, + composition.ime_text_spans[1].thickness); + } + + HRESULT LockGranted3(DWORD flags) { + GetTextTest(0, -1, L"axyZCPc", 7); + + text_spans()->clear(); + *edit_flag() = true; + *committed_size() = 7; + + return S_OK; + } + + void InsertText3(const base::string16& text) { EXPECT_EQ(L"ZCPc", text); } + + void SetCompositionText3(const ui::CompositionText& composition) { + EXPECT_EQ(L"", composition.text); + EXPECT_EQ(0u, composition.selection.start()); + EXPECT_EQ(0u, composition.selection.end()); + EXPECT_EQ(0u, composition.ime_text_spans.size()); + } + + private: + DISALLOW_COPY_AND_ASSIGN(ScenarioTestCallback); +}; + +TEST_F(TSFTextStoreTest, ScenarioTest) { + ScenarioTestCallback callback(text_store_.get()); + EXPECT_CALL(text_input_client_, SetCompositionText(_)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText1)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText2)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::SetCompositionText3)); + + EXPECT_CALL(text_input_client_, InsertText(_)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText2)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::InsertText3)); + + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted1)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted2)) + .WillOnce(Invoke(&callback, &ScenarioTestCallback::LockGranted3)); + + // OnSelectionChange will be called once after LockGranted3(). + EXPECT_CALL(*sink_, OnSelectionChange()).WillOnce(Return(S_OK)); + + // OnLayoutChange will be called once after LockGranted3(). + EXPECT_CALL(*sink_, OnLayoutChange(_, _)).WillOnce(Return(S_OK)); + + // OnTextChange will be called once after LockGranted3(). + EXPECT_CALL(*sink_, OnTextChange(_, _)).WillOnce(Return(S_OK)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(S_OK, result); + result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(S_OK, result); + result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READWRITE, &result)); + EXPECT_EQ(S_OK, result); +} + +class GetTextExtTestCallback : public TSFTextStoreTestCallback { + public: + explicit GetTextExtTestCallback(TSFTextStore* text_store) + : TSFTextStoreTestCallback(text_store), + layout_prepared_character_num_(0) {} + + HRESULT LockGranted(DWORD flags) { + SetInternalState(L"0123456789012", 0, 0, 0); + layout_prepared_character_num_ = 13; + + TsViewCookie view_cookie = 0; + EXPECT_EQ(S_OK, text_store_->GetActiveView(&view_cookie)); + GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20); + GetTextExtTest(view_cookie, 0, 1, 11, 12, 20, 20); + GetTextExtTest(view_cookie, 0, 2, 11, 12, 30, 20); + GetTextExtTest(view_cookie, 9, 9, 100, 12, 100, 20); + GetTextExtTest(view_cookie, 9, 10, 101, 12, 110, 20); + GetTextExtTest(view_cookie, 10, 10, 110, 12, 110, 20); + GetTextExtTest(view_cookie, 11, 11, 20, 112, 20, 120); + GetTextExtTest(view_cookie, 11, 12, 21, 112, 30, 120); + GetTextExtTest(view_cookie, 9, 12, 101, 12, 30, 120); + GetTextExtTest(view_cookie, 9, 13, 101, 12, 40, 120); + GetTextExtTest(view_cookie, 0, 13, 11, 12, 40, 120); + GetTextExtTest(view_cookie, 13, 13, 40, 112, 40, 120); + + layout_prepared_character_num_ = 12; + GetTextExtNoLayoutTest(view_cookie, 13, 13); + + layout_prepared_character_num_ = 0; + GetTextExtNoLayoutTest(view_cookie, 0, 0); + + SetInternalState(L"", 0, 0, 0); + GetTextExtTest(view_cookie, 0, 0, 1, 2, 4, 6); + + // Last character is not availabe due to timing issue of async API. + // In this case, we will get first character bounds instead of whole text + // bounds. + SetInternalState(L"abc", 0, 0, 3); + layout_prepared_character_num_ = 2; + GetTextExtTest(view_cookie, 0, 0, 11, 12, 11, 20); + + // TODO(nona, kinaba): Remove following test case after PPAPI supporting + // GetCompositionCharacterBounds. + SetInternalState(L"a", 0, 0, 1); + layout_prepared_character_num_ = 0; + GetTextExtTest(view_cookie, 0, 1, 1, 2, 4, 6); + return S_OK; + } + + bool GetCompositionCharacterBounds(uint32_t index, gfx::Rect* rect) { + if (index >= layout_prepared_character_num_) + return false; + rect->set_x((index % 10) * 10 + 11); + rect->set_y((index / 10) * 100 + 12); + rect->set_width(9); + rect->set_height(8); + return true; + } + + gfx::Rect GetCaretBounds() { return gfx::Rect(1, 2, 3, 4); } + + private: + uint32_t layout_prepared_character_num_; + + DISALLOW_COPY_AND_ASSIGN(GetTextExtTestCallback); +}; + +TEST_F(TSFTextStoreTest, GetTextExtTest) { + GetTextExtTestCallback callback(text_store_.get()); + EXPECT_CALL(text_input_client_, GetCaretBounds()) + .WillRepeatedly( + Invoke(&callback, &GetTextExtTestCallback::GetCaretBounds)); + + EXPECT_CALL(text_input_client_, GetCompositionCharacterBounds(_, _)) + .WillRepeatedly(Invoke( + &callback, &GetTextExtTestCallback::GetCompositionCharacterBounds)); + + EXPECT_CALL(*sink_, OnLockGranted(_)) + .WillOnce(Invoke(&callback, &GetTextExtTestCallback::LockGranted)); + + HRESULT result = kInvalidResult; + EXPECT_EQ(S_OK, text_store_->RequestLock(TS_LF_READ, &result)); + EXPECT_EQ(S_OK, result); +} + +TEST_F(TSFTextStoreTest, RequestSupportedAttrs) { + EXPECT_CALL(text_input_client_, GetTextInputType()) + .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT)); + EXPECT_CALL(text_input_client_, GetTextInputMode()) + .WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT)); + + EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 1, nullptr)); + + const TS_ATTRID kUnknownAttributes[] = {GUID_NULL}; + EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs( + 0, arraysize(kUnknownAttributes), kUnknownAttributes)) + << "Must fail for unknown attributes"; + + const TS_ATTRID kAttributes[] = {GUID_NULL, GUID_PROP_INPUTSCOPE, GUID_NULL}; + EXPECT_EQ(S_OK, text_store_->RequestSupportedAttrs(0, arraysize(kAttributes), + kAttributes)) + << "InputScope must be supported"; + + { + SCOPED_TRACE("Check if RequestSupportedAttrs fails while focus is lost"); + // Emulate focus lost + text_store_->SetFocusedTextInputClient(nullptr, nullptr); + EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs(0, 0, nullptr)); + EXPECT_HRESULT_FAILED(text_store_->RequestSupportedAttrs( + 0, arraysize(kAttributes), kAttributes)); + } +} + +TEST_F(TSFTextStoreTest, RetrieveRequestedAttrs) { + EXPECT_CALL(text_input_client_, GetTextInputType()) + .WillRepeatedly(Return(TEXT_INPUT_TYPE_TEXT)); + EXPECT_CALL(text_input_client_, GetTextInputMode()) + .WillRepeatedly(Return(TEXT_INPUT_MODE_DEFAULT)); + + ULONG num_copied = 0xfffffff; + EXPECT_HRESULT_FAILED( + text_store_->RetrieveRequestedAttrs(1, nullptr, &num_copied)); + + { + SCOPED_TRACE("Make sure if InputScope is supported"); + TS_ATTRVAL buffer[2] = {}; + num_copied = 0xfffffff; + ASSERT_EQ(S_OK, text_store_->RetrieveRequestedAttrs(arraysize(buffer), + buffer, &num_copied)); + bool input_scope_found = false; + for (size_t i = 0; i < num_copied; ++i) { + base::win::ScopedVariant variant; + // Move ownership from |buffer[i].varValue| to |variant|. + std::swap(*variant.Receive(), buffer[i].varValue); + if (IsEqualGUID(buffer[i].idAttr, GUID_PROP_INPUTSCOPE)) { + EXPECT_EQ(VT_UNKNOWN, variant.type()); + Microsoft::WRL::ComPtr<ITfInputScope> input_scope; + EXPECT_HRESULT_SUCCEEDED(variant.AsInput()->punkVal->QueryInterface( + IID_PPV_ARGS(&input_scope))); + input_scope_found = true; + // we do not break here to clean up all the retrieved VARIANTs. + } + } + EXPECT_TRUE(input_scope_found); + } + { + SCOPED_TRACE("Check if RetrieveRequestedAttrs fails while focus is lost"); + // Emulate focus lost + text_store_->SetFocusedTextInputClient(nullptr, nullptr); + num_copied = 0xfffffff; + TS_ATTRVAL buffer[2] = {}; + EXPECT_HRESULT_FAILED(text_store_->RetrieveRequestedAttrs( + arraysize(buffer), buffer, &num_copied)); + } +} + +} // namespace +} // namespace ui |