1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #ifndef UI_BASE_IME_WIN_TSF_TEXT_STORE_H_
6 #define UI_BASE_IME_WIN_TSF_TEXT_STORE_H_
7
8 #include <msctf.h>
9 #include <wrl/client.h>
10 #include <deque>
11
12 #include "base/compiler_specific.h"
13 #include "base/component_export.h"
14 #include "base/macros.h"
15 #include "base/strings/string16.h"
16 #include "ui/base/ime/ime_text_span.h"
17 #include "ui/base/ime/input_method_delegate.h"
18 #include "ui/events/event_utils.h"
19 #include "ui/gfx/range/range.h"
20
21 namespace ui {
22 class TextInputClient;
23
24 // TSFTextStore is used to interact with the input method via TSF manager.
25 // TSFTextStore have a string buffer which is manipulated by TSF manager through
26 // ITextStoreACP interface methods such as SetText().
27 // When the input method updates the composition, TSFTextStore calls
28 // TextInputClient::SetCompositionText(). And when the input method finishes the
29 // composition, TSFTextStore calls TextInputClient::InsertText().
30 //
31 // How TSFTextStore works:
32 // - Assume the document is empty and in focus.
33 // - The user enters "a".
34 // - The input method set composition as "a".
35 // - TSF manager calls TSFTextStore::RequestLock().
36 // - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
37 // - In OnLockGranted(), TSF manager calls
38 // - TSFTextStore::OnStartComposition()
39 // - TSFTextStore::SetText()
40 // The pending string buffer is set as "a".
41 // The document whole buffer is set as "a".
42 // - TSFTextStore::OnUpdateComposition()
43 // - TSFTextStore::OnEndEdit()
44 // TSFTextStore can get the composition information such as underlines.
45 // - TSFTextStore calls TextInputClient::SetCompositionText().
46 // "a" is shown with an underline as composition string.
47 // - The user enters 'b'.
48 // - The input method set composition as "ab".
49 // - TSF manager calls TSFTextStore::RequestLock().
50 // - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
51 // - In OnLockGranted(), TSF manager calls
52 // - TSFTextStore::SetText()
53 // The pending string buffer is set as "b".
54 // The document whole buffer is changed to "ab".
55 // - TSFTextStore::OnUpdateComposition()
56 // - TSFTextStore::OnEndEdit()
57 // - TSFTextStore calls TextInputClient::SetCompositionText().
58 // "ab" is shown with an underline as composition string.
59 // - The user enters <space>.
60 // - The input method set composition as "aB".
61 // - TSF manager calls TSFTextStore::RequestLock().
62 // - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
63 // - In OnLockGranted(), TSF manager calls
64 // - TSFTextStore::SetText()
65 // The pending string buffer is set as "B".
66 // The document whole buffer is changed to "aB".
67 // - TSFTextStore::OnUpdateComposition()
68 // - TSFTextStore::OnEndEdit()
69 // - TSFTextStore calls TextInputClient::SetCompositionText().
70 // "aB" is shown with an underline as composition string.
71 // - The user enters <enter>.
72 // - The input method commits "aB".
73 // - TSF manager calls TSFTextStore::RequestLock().
74 // - TSFTextStore callbacks ITextStoreACPSink::OnLockGranted().
75 // - In OnLockGranted(), TSF manager calls
76 // - TSFTextStore::OnEndComposition()
77 // - TSFTextStore::OnEndEdit()
78 // TSFTextStore knows "aB" is committed.
79 // - TSFTextStore calls TextInputClient::InsertText().
80 // "aB" is shown as committed string.
81 // - TSFTextStore clears the pending string buffer.
82 // - TSFTextStore verified if the document whole buffer is the same as the
83 // buffer returned from TextInputClient. If the buffer is different, then
84 // call OnSelectionChange(), OnLayoutChange() and
85 // OnTextChange() of ITextStoreACPSink to let TSF manager know that the
86 // string buffer has been changed other than IME.
87 //
88 // About the locking scheme:
89 // When TSF manager manipulates the string buffer it calls RequestLock() to get
90 // the lock of the document. If TSFTextStore can grant the lock request, it
91 // callbacks ITextStoreACPSink::OnLockGranted().
92 // RequestLock() is called from only one thread, but called recursively in
93 // OnLockGranted() or OnSelectionChange() or OnLayoutChange() or OnTextChange().
94 // If the document is locked and the lock request is asynchronous, TSFTextStore
95 // queues the request. The queued requests will be handled after the current
96 // lock is removed.
97 // More information about document locks can be found here:
98 // http://msdn.microsoft.com/en-us/library/ms538064
99 //
100 // More information about TSF can be found here:
101 // http://msdn.microsoft.com/en-us/library/ms629032
COMPONENT_EXPORT(UI_BASE_IME_WIN)102 class COMPONENT_EXPORT(UI_BASE_IME_WIN) TSFTextStore
103 : public ITextStoreACP,
104 public ITfContextOwnerCompositionSink,
105 public ITfKeyTraceEventSink,
106 public ITfTextEditSink {
107 public:
108 TSFTextStore();
109 virtual ~TSFTextStore();
110 HRESULT Initialize();
111
112 // ITextStoreACP:
113 IFACEMETHODIMP_(ULONG) AddRef() override;
114 IFACEMETHODIMP_(ULONG) Release() override;
115 IFACEMETHODIMP QueryInterface(REFIID iid, void** ppv) override;
116 IFACEMETHODIMP AdviseSink(REFIID iid, IUnknown* unknown, DWORD mask) override;
117 IFACEMETHODIMP FindNextAttrTransition(LONG acp_start,
118 LONG acp_halt,
119 ULONG num_filter_attributes,
120 const TS_ATTRID* filter_attributes,
121 DWORD flags,
122 LONG* acp_next,
123 BOOL* found,
124 LONG* found_offset) override;
125 IFACEMETHODIMP GetACPFromPoint(TsViewCookie view_cookie,
126 const POINT* point,
127 DWORD flags,
128 LONG* acp) override;
129 IFACEMETHODIMP GetActiveView(TsViewCookie* view_cookie) override;
130 IFACEMETHODIMP GetEmbedded(LONG acp_pos,
131 REFGUID service,
132 REFIID iid,
133 IUnknown** unknown) override;
134 IFACEMETHODIMP GetEndACP(LONG* acp) override;
135 IFACEMETHODIMP GetFormattedText(LONG acp_start,
136 LONG acp_end,
137 IDataObject** data_object) override;
138 IFACEMETHODIMP GetScreenExt(TsViewCookie view_cookie, RECT* rect) override;
139 IFACEMETHODIMP GetSelection(ULONG selection_index,
140 ULONG selection_buffer_size,
141 TS_SELECTION_ACP* selection_buffer,
142 ULONG* fetched_count) override;
143 IFACEMETHODIMP GetStatus(TS_STATUS* pdcs) override;
144 IFACEMETHODIMP GetText(LONG acp_start,
145 LONG acp_end,
146 wchar_t* text_buffer,
147 ULONG text_buffer_size,
148 ULONG* text_buffer_copied,
149 TS_RUNINFO* run_info_buffer,
150 ULONG run_info_buffer_size,
151 ULONG* run_info_buffer_copied,
152 LONG* next_acp) override;
153 IFACEMETHODIMP GetTextExt(TsViewCookie view_cookie,
154 LONG acp_start,
155 LONG acp_end,
156 RECT* rect,
157 BOOL* clipped) override;
158 IFACEMETHODIMP GetWnd(TsViewCookie view_cookie, HWND* window_handle) override;
159 IFACEMETHODIMP InsertEmbedded(DWORD flags,
160 LONG acp_start,
161 LONG acp_end,
162 IDataObject* data_object,
163 TS_TEXTCHANGE* change) override;
164 IFACEMETHODIMP InsertEmbeddedAtSelection(DWORD flags,
165 IDataObject* data_object,
166 LONG* acp_start,
167 LONG* acp_end,
168 TS_TEXTCHANGE* change) override;
169 IFACEMETHODIMP InsertTextAtSelection(DWORD flags,
170 const wchar_t* text_buffer,
171 ULONG text_buffer_size,
172 LONG* acp_start,
173 LONG* acp_end,
174 TS_TEXTCHANGE* text_change) override;
175 IFACEMETHODIMP QueryInsert(LONG acp_test_start,
176 LONG acp_test_end,
177 ULONG text_size,
178 LONG* acp_result_start,
179 LONG* acp_result_end) override;
180 IFACEMETHODIMP QueryInsertEmbedded(const GUID* service,
181 const FORMATETC* format,
182 BOOL* insertable) override;
183 IFACEMETHODIMP RequestAttrsAtPosition(LONG acp_pos,
184 ULONG attribute_buffer_size,
185 const TS_ATTRID* attribute_buffer,
186 DWORD flags) override;
187 IFACEMETHODIMP RequestAttrsTransitioningAtPosition(
188 LONG acp_pos,
189 ULONG attribute_buffer_size,
190 const TS_ATTRID* attribute_buffer,
191 DWORD flags) override;
192 IFACEMETHODIMP RequestLock(DWORD lock_flags, HRESULT* result) override;
193 IFACEMETHODIMP RequestSupportedAttrs(
194 DWORD flags,
195 ULONG attribute_buffer_size,
196 const TS_ATTRID* attribute_buffer) override;
197 IFACEMETHODIMP RetrieveRequestedAttrs(
198 ULONG attribute_buffer_size,
199 TS_ATTRVAL* attribute_buffer,
200 ULONG* attribute_buffer_copied) override;
201 IFACEMETHODIMP SetSelection(
202 ULONG selection_buffer_size,
203 const TS_SELECTION_ACP* selection_buffer) override;
204 IFACEMETHODIMP SetText(DWORD flags,
205 LONG acp_start,
206 LONG acp_end,
207 const wchar_t* text_buffer,
208 ULONG text_buffer_size,
209 TS_TEXTCHANGE* text_change) override;
210 IFACEMETHODIMP UnadviseSink(IUnknown* unknown) override;
211
212 // ITfContextOwnerCompositionSink:
213 IFACEMETHODIMP OnStartComposition(ITfCompositionView* composition_view,
214 BOOL* ok) override;
215 IFACEMETHODIMP OnUpdateComposition(ITfCompositionView* composition_view,
216 ITfRange* range) override;
217 IFACEMETHODIMP OnEndComposition(
218 ITfCompositionView* composition_view) override;
219
220 // ITfTextEditSink:
221 IFACEMETHODIMP OnEndEdit(ITfContext* context,
222 TfEditCookie read_only_edit_cookie,
223 ITfEditRecord* edit_record) override;
224
225 // ITfKeyTraceEventSink
226 IFACEMETHODIMP OnKeyTraceDown(WPARAM wParam, LPARAM lParam) override;
227 IFACEMETHODIMP OnKeyTraceUp(WPARAM wParam, LPARAM lParam) override;
228
229 // Called after |TSFBridgeImpl::CreateDocumentManager| to tell that the
230 // text-store is successfully associated with a Context.
231 void OnContextInitialized(ITfContext* context);
232
233 // Sets currently focused TextInputClient.
234 void SetFocusedTextInputClient(HWND focused_window,
235 TextInputClient* text_input_client);
236 // Removes currently focused TextInputClient.
237 void RemoveFocusedTextInputClient(TextInputClient* text_input_client);
238
239 // Sets InputMethodDelegate pointer.
240 void SetInputMethodDelegate(internal::InputMethodDelegate* delegate);
241
242 // Removes InputMethodDelegate pointer.
243 void RemoveInputMethodDelegate();
244
245 // Cancels the ongoing composition if exists.
246 bool CancelComposition();
247
248 // Confirms the ongoing composition if exists.
249 bool ConfirmComposition();
250
251 // Sends OnLayoutChange() via |text_store_acp_sink_|.
252 void SendOnLayoutChange();
253
254 void SetInputPanelPolicy(bool input_panel_policy_manual);
255
256 private:
257 friend class TSFTextStoreTest;
258 friend class TSFTextStoreTestCallback;
259
260 // Terminate an active composition for this text store.
261 bool TerminateComposition();
262
263 // Compare our cached text buffer and selection with the up-to-date
264 // text buffer and selection from TextInputClient. We also update
265 // cached text buffer and selection with the new version. Then notify
266 // input service about the change.
267 void CalculateTextandSelectionDiffAndNotifyIfNeeded();
268
269 // Synthesize keyevent and send to text input client to fire corresponding
270 // javascript keyevent during composition.
271 void DispatchKeyEvent(ui::EventType type, WPARAM wparam, LPARAM lparam);
272
273 // Start new composition on existing text.
274 void StartCompositionOnExistingText() const;
275
276 // Start new composition with new text.
277 void StartCompositionOnNewText(size_t start_offset,
278 const base::string16& composition_string);
279
280 // Commit and insert text into TextInputClient. End any ongoing composition.
281 void CommitTextAndEndCompositionIfAny(size_t old_size, size_t new_size) const;
282
283 // Checks if the document has a read-only lock.
284 bool HasReadLock() const;
285
286 // Checks if the document has a read and write lock.
287 bool HasReadWriteLock() const;
288
289 // Gets the display attribute structure.
290 bool GetDisplayAttribute(TfGuidAtom guid_atom,
291 TF_DISPLAYATTRIBUTE* attribute);
292
293 // Gets the committed string size and underline information of the context.
294 bool GetCompositionStatus(ITfContext* context,
295 const TfEditCookie read_only_edit_cookie,
296 size_t* committed_size,
297 ImeTextSpans* spans);
298
299 // Reset all cached flags when |TSFTextStore::RequestLock| returns.
300 void ResetCacheAfterEditSession();
301
302 // Gets the style information from the display attribute for the actively
303 // composed text.
304 void GetStyle(const TF_DISPLAYATTRIBUTE& attribute, ImeTextSpan* span);
305
306 // The reference count of this instance.
307 volatile LONG ref_count_ = 0;
308
309 // A pointer of ITextStoreACPSink, this instance is given in AdviseSink.
310 Microsoft::WRL::ComPtr<ITextStoreACPSink> text_store_acp_sink_;
311
312 // The current mask of |text_store_acp_sink_|.
313 DWORD text_store_acp_sink_mask_ = 0;
314
315 // HWND of the current view window which is set in SetFocusedTextInputClient.
316 HWND window_handle_ = nullptr;
317
318 // Current TextInputClient which is set in SetFocusedTextInputClient.
319 TextInputClient* text_input_client_ = nullptr;
320
321 // InputMethodDelegate instance which is used dispatch key events.
322 internal::InputMethodDelegate* input_method_delegate_ = nullptr;
323
324 // |string_buffer_document_| contains all string in current active view.
325 // |string_pending_insertion_| contains only string in current edit session.
326 // |composition_start_| indicates the location for a composition to start at.
327 // |has_composition_range_| indicates the state of composition.
328 // |composition_range_| indicates the range of composition if any.
329 // Example: "aoi" is committed, and "umi" is under composition.
330 // In current edit session, user press "i" on keyboard.
331 // |string_buffer_document_|: "aoiumi"
332 // |string_pending_insertion_| : "i"
333 // |composition_start_|: 3
334 // |has_composition_range_| = true;
335 // |composition_range_start_| = 3;
336 // |composition_range_end_| = 6;
337 base::string16 string_buffer_document_;
338 base::string16 string_pending_insertion_;
339 size_t composition_start_ = 0;
340 bool has_composition_range_ = false;
341 gfx::Range composition_range_;
342
343 // |on_start_composition_called_| indicates that OnStartComposition() is
344 // called duriing current edit session.
345 bool on_start_composition_called_ = false;
346
347 // |previous_composition_string_| indicicates composition string in last
348 // edit session during same composition. |previous_composition_start_|
349 // indicates composition start in last session during same composition. If
350 // RequestLock() is called during two edit sessions, we don't want to set same
351 // composition string twice. |previous_composition_selection_range_| indicates
352 // the selection range during composition. We want to send the selection
353 // change to blink if IME only change the selection range but not the
354 // composition text. |previous_text_spans_| saves the IME spans in previous
355 // edit session during same composition.
356 base::string16 previous_composition_string_;
357 size_t previous_composition_start_ = 0;
358 gfx::Range previous_composition_selection_range_ = gfx::Range::InvalidRange();
359 ImeTextSpans previous_text_spans_;
360
361 // |new_text_inserted_| indicates there is text to be inserted
362 // into blink during ITextStoreACP::SetText().
363 // |replace_text_range_| indicates the start and end offsets of the text to be
364 // replaced by the new text to be inserted.
365 // |replace_text_size_| indicates the size of the text to be inserted.
366 // Example: "k" is going to replace "i"
367 // |string_buffer_document_|: "aeiou"
368 // |new_text_inserted_|: true
369 // |replace_text_range_start_|: 2
370 // |replace_text_range_end_|: 3
371 // |replace_text_size_|: 1
372 bool new_text_inserted_ = false;
373 gfx::Range replace_text_range_;
374 size_t replace_text_size_;
375
376 // |buffer_from_client_| contains all string returned from
377 // TextInputClient::GetTextFromRange();
378 base::string16 buffer_from_client_;
379
380 // |selection_from_client_| indicates the selection range returned from
381 // TextInputClient::GetEditableSelectionRange();
382 gfx::Range selection_from_client_;
383
384 // |composition_range_from_client_| indicates the composition range returned
385 // from TextInputClient::GetCompositionTextRange();
386 gfx::Range composition_from_client_;
387
388 // |wparam_keydown_cached_| and |lparam_keydown_cached_| contains key event
389 // info that is used to synthesize key event during composition.
390 // |wparam_keydown_fired_| indicates if a keydown event has been fired.
391 WPARAM wparam_keydown_cached_ = 0;
392 LPARAM lparam_keydown_cached_ = 0;
393 WPARAM wparam_keydown_fired_ = 0;
394
395 // |selection_start_| and |selection_end_| indicates the selection range.
396 // Example: "iue" is selected
397 // |string_buffer_document_|: "aiueo"
398 // |selection_.start()|: 1
399 // |selection_.end()|: 4
400 gfx::Range selection_;
401
402 // Indicates if the selection is an interim character. Please refer to
403 // https://docs.microsoft.com/en-us/windows/win32/api/textstor/ns-textstor-ts_selectionstyle
404 bool is_selection_interim_char_ = false;
405
406 // |start_offset| and |end_offset| of |text_spans_| indicates
407 // the offsets in |string_buffer_document_|.
408 // Example: "aoi" is committed. There are two underlines in "umi" and "no".
409 // |string_buffer_document_|: "aoiumino"
410 // |composition_start_|: 3
411 // text_spans_[0].start_offset: 3
412 // text_spans_[0].end_offset: 6
413 // text_spans_[1].start_offset: 6
414 // text_spans_[1].end_offset: 8
415 ImeTextSpans text_spans_;
416
417 // |edit_flag_| indicates that the status is edited during
418 // ITextStoreACPSink::OnLockGranted().
419 bool edit_flag_ = false;
420
421 // Checks for re-entrancy while notifying changes to TSF.
422 bool is_notification_in_progress_ = false;
423
424 // Checks for re-entrancy while writing to text input client.
425 bool is_tic_write_in_progress_ = false;
426
427 // The type of current lock.
428 // 0: No lock.
429 // TS_LF_READ: read-only lock.
430 // TS_LF_READWRITE: read/write lock.
431 DWORD current_lock_type_ = 0;
432
433 // Queue of the lock request used in RequestLock().
434 std::deque<DWORD> lock_queue_;
435
436 // Category manager and Display attribute manager are used to obtain the
437 // attributes of the composition string.
438 Microsoft::WRL::ComPtr<ITfCategoryMgr> category_manager_;
439 Microsoft::WRL::ComPtr<ITfDisplayAttributeMgr> display_attribute_manager_;
440 Microsoft::WRL::ComPtr<ITfContext> context_;
441
442 // input_panel_policy_manual_ equals to false would make the SIP policy
443 // to automatic meaning TSF would raise/dismiss the SIP based on TSFTextStore
444 // focus and other heuristics that input service have added on Windows to
445 // provide a consistent behavior across all apps on Windows.
446 // input_panel_policy_manual_ equals to true would make the SIP policy to
447 // manual meaning TSF wouldn't raise/dismiss the SIP automatically. This is
448 // used to control the SIP behavior based on user interaction with the page.
449 bool input_panel_policy_manual_ = true;
450
451 DISALLOW_COPY_AND_ASSIGN(TSFTextStore);
452 };
453
454 } // namespace ui
455
456 #endif // UI_BASE_IME_WIN_TSF_TEXT_STORE_H_
457