summaryrefslogtreecommitdiff
path: root/chromium/ui/base/ime
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:20:33 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2018-05-15 10:28:57 +0000
commitd17ea114e5ef69ad5d5d7413280a13e6428098aa (patch)
tree2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/ui/base/ime
parent8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff)
downloadqtwebengine-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')
-rw-r--r--chromium/ui/base/ime/BUILD.gn44
-rw-r--r--chromium/ui/base/ime/chromeos/public/interfaces/BUILD.gn11
-rw-r--r--chromium/ui/base/ime/chromeos/public/interfaces/ime_keyset.mojom15
-rw-r--r--chromium/ui/base/ime/composition_text_unittest.cc14
-rw-r--r--chromium/ui/base/ime/composition_text_util_pango.cc15
-rw-r--r--chromium/ui/base/ime/composition_text_util_pango_unittest.cc56
-rw-r--r--chromium/ui/base/ime/dummy_input_method.cc5
-rw-r--r--chromium/ui/base/ime/dummy_input_method.h7
-rw-r--r--chromium/ui/base/ime/ime_engine_handler_interface.h4
-rw-r--r--chromium/ui/base/ime/ime_text_span.cc14
-rw-r--r--chromium/ui/base/ime/ime_text_span.h18
-rw-r--r--chromium/ui/base/ime/input_method.h7
-rw-r--r--chromium/ui/base/ime/input_method_auralinux.cc21
-rw-r--r--chromium/ui/base/ime/input_method_auralinux.h2
-rw-r--r--chromium/ui/base/ime/input_method_base.cc23
-rw-r--r--chromium/ui/base/ime/input_method_base.h12
-rw-r--r--chromium/ui/base/ime/input_method_base_unittest.cc5
-rw-r--r--chromium/ui/base/ime/input_method_chromeos.cc99
-rw-r--r--chromium/ui/base/ime/input_method_chromeos.h26
-rw-r--r--chromium/ui/base/ime/input_method_chromeos_unittest.cc162
-rw-r--r--chromium/ui/base/ime/input_method_factory.cc4
-rw-r--r--chromium/ui/base/ime/input_method_initializer.cc14
-rw-r--r--chromium/ui/base/ime/input_method_mac.h2
-rw-r--r--chromium/ui/base/ime/input_method_mac.mm5
-rw-r--r--chromium/ui/base/ime/input_method_minimal.cc6
-rw-r--r--chromium/ui/base/ime/input_method_minimal.h2
-rw-r--r--chromium/ui/base/ime/input_method_win.cc256
-rw-r--r--chromium/ui/base/ime/input_method_win.h39
-rw-r--r--chromium/ui/base/ime/input_method_win_base.cc276
-rw-r--r--chromium/ui/base/ime/input_method_win_base.h67
-rw-r--r--chromium/ui/base/ime/input_method_win_tsf.cc147
-rw-r--r--chromium/ui/base/ime/input_method_win_tsf.h58
-rw-r--r--chromium/ui/base/ime/linux/fake_input_method_context_factory.cc1
-rw-r--r--chromium/ui/base/ime/mock_input_method.cc5
-rw-r--r--chromium/ui/base/ime/mock_input_method.h7
-rw-r--r--chromium/ui/base/ime/win/imm32_manager.cc12
-rw-r--r--chromium/ui/base/ime/win/mock_tsf_bridge.cc70
-rw-r--r--chromium/ui/base/ime/win/mock_tsf_bridge.h99
-rw-r--r--chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.cc30
-rw-r--r--chromium/ui/base/ime/win/on_screen_keyboard_display_manager_stub.h35
-rw-r--r--chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.cc392
-rw-r--r--chromium/ui/base/ime/win/on_screen_keyboard_display_manager_tab_tip.h51
-rw-r--r--chromium/ui/base/ime/win/on_screen_keyboard_display_manager_unittest.cc53
-rw-r--r--chromium/ui/base/ime/win/osk_display_manager.cc31
-rw-r--r--chromium/ui/base/ime/win/osk_display_manager.h47
-rw-r--r--chromium/ui/base/ime/win/osk_display_observer.h27
-rw-r--r--chromium/ui/base/ime/win/tsf_bridge.cc542
-rw-r--r--chromium/ui/base/ime/win/tsf_bridge.h94
-rw-r--r--chromium/ui/base/ime/win/tsf_event_router.cc296
-rw-r--r--chromium/ui/base/ime/win/tsf_event_router.h78
-rw-r--r--chromium/ui/base/ime/win/tsf_text_store.cc925
-rw-r--r--chromium/ui/base/ime/win/tsf_text_store.h311
-rw-r--r--chromium/ui/base/ime/win/tsf_text_store_unittest.cc1302
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