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