• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 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 #include <msctf.h>
6 
7 #include <map>
8 
9 #include "base/logging.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/threading/thread_local_storage.h"
14 #include "base/win/scoped_comptr.h"
15 #include "base/win/scoped_variant.h"
16 #include "ui/base/ime/text_input_client.h"
17 #include "ui/base/ime/win/tsf_bridge.h"
18 #include "ui/base/ime/win/tsf_text_store.h"
19 
20 namespace ui {
21 
22 namespace {
23 
24 // We use thread local storage for TSFBridge lifespan management.
25 base::ThreadLocalStorage::StaticSlot tls_tsf_bridge = TLS_INITIALIZER;
26 
27 
28 // TsfBridgeDelegate -----------------------------------------------------------
29 
30 // A TLS implementation of TSFBridge.
31 class TSFBridgeDelegate : public TSFBridge {
32  public:
33   TSFBridgeDelegate();
34   virtual ~TSFBridgeDelegate();
35 
36   bool Initialize();
37 
38   // TsfBridge:
39   virtual void OnTextInputTypeChanged(const TextInputClient* client) OVERRIDE;
40   virtual void OnTextLayoutChanged() OVERRIDE;
41   virtual bool CancelComposition() OVERRIDE;
42   virtual bool ConfirmComposition() OVERRIDE;
43   virtual void SetFocusedClient(HWND focused_window,
44                                 TextInputClient* client) OVERRIDE;
45   virtual void RemoveFocusedClient(TextInputClient* client) OVERRIDE;
46   virtual base::win::ScopedComPtr<ITfThreadMgr> GetThreadManager() OVERRIDE;
47   virtual TextInputClient* GetFocusedTextInputClient() const OVERRIDE;
48 
49  private:
50   // Returns true if |tsf_document_map_| is successfully initialized. This
51   // method should be called from and only from Initialize().
52   bool InitializeDocumentMapInternal();
53 
54   // Returns true if |context| is successfully updated to be a disabled
55   // context, where an IME should be deactivated. This is suitable for some
56   // special input context such as password fields.
57   bool InitializeDisabledContext(ITfContext* context);
58 
59   // Returns true if a TSF document manager and a TSF context is successfully
60   // created with associating with given |text_store|. The returned
61   // |source_cookie| indicates the binding between |text_store| and |context|.
62   // You can pass NULL to |text_store| and |source_cookie| when text store is
63   // not necessary.
64   bool CreateDocumentManager(TSFTextStore* text_store,
65                              ITfDocumentMgr** document_manager,
66                              ITfContext** context,
67                              DWORD* source_cookie);
68 
69   // Returns true if |document_manager| is the focused document manager.
70   bool IsFocused(ITfDocumentMgr* document_manager);
71 
72   // Returns true if already initialized.
73   bool IsInitialized();
74 
75   // Updates or clears the association maintained in the TSF runtime between
76   // |attached_window_handle_| and the current document manager. Keeping this
77   // association updated solves some tricky event ordering issues between
78   // logical text input focus managed by Chrome and native text input focus
79   // managed by the OS.
80   // Background:
81   //   TSF runtime monitors some Win32 messages such as WM_ACTIVATE to
82   //   change the focused document manager. This is problematic when
83   //   TSFBridge::SetFocusedClient is called first then the target window
84   //   receives WM_ACTIVATE. This actually occurs in Aura environment where
85   //   WM_NCACTIVATE is used as a trigger to restore text input focus.
86   // Caveats:
87   //   TSF runtime does not increment the reference count of the attached
88   //   document manager. See the comment inside the method body for
89   //   details.
90   void UpdateAssociateFocus();
91   void ClearAssociateFocus();
92 
93   // A triple of document manager, text store and binding cookie between
94   // a context owned by the document manager and the text store. This is a
95   // minimum working set of an editable document in TSF.
96   struct TSFDocument {
97    public:
TSFDocumentui::__anon29a604200111::TSFBridgeDelegate::TSFDocument98     TSFDocument() : cookie(TF_INVALID_COOKIE) {}
TSFDocumentui::__anon29a604200111::TSFBridgeDelegate::TSFDocument99     TSFDocument(const TSFDocument& src)
100         : document_manager(src.document_manager),
101           cookie(src.cookie) {}
102     base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
103     scoped_refptr<TSFTextStore> text_store;
104     DWORD cookie;
105   };
106 
107   // Returns a pointer to TSFDocument that is associated with the current
108   // TextInputType of |client_|.
109   TSFDocument* GetAssociatedDocument();
110 
111   // An ITfThreadMgr object to be used in focus and document management.
112   base::win::ScopedComPtr<ITfThreadMgr> thread_manager_;
113 
114   // A map from TextInputType to an editable document for TSF. We use multiple
115   // TSF documents that have different InputScopes and TSF attributes based on
116   // the TextInputType associated with the target document. For a TextInputType
117   // that is not coverted by this map, a default document, e.g. the document
118   // for TEXT_INPUT_TYPE_TEXT, should be used.
119   // Note that some IMEs don't change their state unless the document focus is
120   // changed. This is why we use multiple documents instead of changing TSF
121   // metadata of a single document on the fly.
122   typedef std::map<TextInputType, TSFDocument> TSFDocumentMap;
123   TSFDocumentMap tsf_document_map_;
124 
125   // An identifier of TSF client.
126   TfClientId client_id_;
127 
128   // Current focused text input client. Do not free |client_|.
129   TextInputClient* client_;
130 
131   // Represents the window that is currently owns text input focus.
132   HWND attached_window_handle_;
133 
134   DISALLOW_COPY_AND_ASSIGN(TSFBridgeDelegate);
135 };
136 
TSFBridgeDelegate()137 TSFBridgeDelegate::TSFBridgeDelegate()
138     : client_id_(TF_CLIENTID_NULL),
139       client_(NULL),
140       attached_window_handle_(NULL) {
141 }
142 
~TSFBridgeDelegate()143 TSFBridgeDelegate::~TSFBridgeDelegate() {
144   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
145   if (!IsInitialized())
146     return;
147   for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
148        it != tsf_document_map_.end(); ++it) {
149     base::win::ScopedComPtr<ITfContext> context;
150     base::win::ScopedComPtr<ITfSource> source;
151     if (it->second.cookie != TF_INVALID_COOKIE &&
152         SUCCEEDED(it->second.document_manager->GetBase(context.Receive())) &&
153         SUCCEEDED(source.QueryFrom(context))) {
154       source->UnadviseSink(it->second.cookie);
155     }
156   }
157   tsf_document_map_.clear();
158 
159   client_id_ = TF_CLIENTID_NULL;
160 }
161 
Initialize()162 bool TSFBridgeDelegate::Initialize() {
163   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
164   if (client_id_ != TF_CLIENTID_NULL) {
165     DVLOG(1) << "Already initialized.";
166     return false;
167   }
168 
169   if (FAILED(thread_manager_.CreateInstance(CLSID_TF_ThreadMgr))) {
170     DVLOG(1) << "Failed to create ThreadManager instance.";
171     return false;
172   }
173 
174   if (FAILED(thread_manager_->Activate(&client_id_))) {
175     DVLOG(1) << "Failed to activate Thread Manager.";
176     return false;
177   }
178 
179   if (!InitializeDocumentMapInternal())
180     return false;
181 
182   // Japanese IME expects the default value of this compartment is
183   // TF_SENTENCEMODE_PHRASEPREDICT like IMM32 implementation. This value is
184   // managed per thread, so that it is enough to set this value at once. This
185   // value does not affect other language's IME behaviors.
186   base::win::ScopedComPtr<ITfCompartmentMgr> thread_compartment_manager;
187   if (FAILED(thread_compartment_manager.QueryFrom(thread_manager_))) {
188     DVLOG(1) << "Failed to get ITfCompartmentMgr.";
189     return false;
190   }
191 
192   base::win::ScopedComPtr<ITfCompartment> sentence_compartment;
193   if (FAILED(thread_compartment_manager->GetCompartment(
194       GUID_COMPARTMENT_KEYBOARD_INPUTMODE_SENTENCE,
195       sentence_compartment.Receive()))) {
196     DVLOG(1) << "Failed to get sentence compartment.";
197     return false;
198   }
199 
200   base::win::ScopedVariant sentence_variant;
201   sentence_variant.Set(TF_SENTENCEMODE_PHRASEPREDICT);
202   if (FAILED(sentence_compartment->SetValue(client_id_, &sentence_variant))) {
203     DVLOG(1) << "Failed to change the sentence mode.";
204     return false;
205   }
206 
207   return true;
208 }
209 
OnTextInputTypeChanged(const TextInputClient * client)210 void TSFBridgeDelegate::OnTextInputTypeChanged(const TextInputClient* client) {
211   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
212   DCHECK(IsInitialized());
213 
214   if (client != client_) {
215     // Called from not focusing client. Do nothing.
216     return;
217   }
218 
219   UpdateAssociateFocus();
220 
221   TSFDocument* document = GetAssociatedDocument();
222   if (!document)
223     return;
224   thread_manager_->SetFocus(document->document_manager.get());
225   OnTextLayoutChanged();
226 }
227 
OnTextLayoutChanged()228 void TSFBridgeDelegate::OnTextLayoutChanged() {
229   TSFDocument* document = GetAssociatedDocument();
230   if (!document)
231     return;
232   if (!document->text_store)
233     return;
234   document->text_store->SendOnLayoutChange();
235 }
236 
CancelComposition()237 bool TSFBridgeDelegate::CancelComposition() {
238   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
239   DCHECK(IsInitialized());
240 
241   TSFDocument* document = GetAssociatedDocument();
242   if (!document)
243     return false;
244   if (!document->text_store)
245     return false;
246 
247   return document->text_store->CancelComposition();
248 }
249 
ConfirmComposition()250 bool TSFBridgeDelegate::ConfirmComposition() {
251   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
252   DCHECK(IsInitialized());
253 
254   TSFDocument* document = GetAssociatedDocument();
255   if (!document)
256     return false;
257   if (!document->text_store)
258     return false;
259 
260   return document->text_store->ConfirmComposition();
261 }
262 
SetFocusedClient(HWND focused_window,TextInputClient * client)263 void TSFBridgeDelegate::SetFocusedClient(HWND focused_window,
264                                          TextInputClient* client) {
265   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
266   DCHECK(client);
267   DCHECK(IsInitialized());
268   if (attached_window_handle_ != focused_window)
269     ClearAssociateFocus();
270   client_ = client;
271   attached_window_handle_ = focused_window;
272 
273   for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
274        it != tsf_document_map_.end(); ++it) {
275     if (it->second.text_store.get() == NULL)
276       continue;
277     it->second.text_store->SetFocusedTextInputClient(focused_window,
278                                                      client);
279   }
280 
281   // Synchronize text input type state.
282   OnTextInputTypeChanged(client);
283 }
284 
RemoveFocusedClient(TextInputClient * client)285 void TSFBridgeDelegate::RemoveFocusedClient(TextInputClient* client) {
286   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
287   DCHECK(IsInitialized());
288   if (client_ != client)
289     return;
290   ClearAssociateFocus();
291   client_ = NULL;
292   attached_window_handle_ = NULL;
293   for (TSFDocumentMap::iterator it = tsf_document_map_.begin();
294        it != tsf_document_map_.end(); ++it) {
295     if (it->second.text_store.get() == NULL)
296       continue;
297     it->second.text_store->SetFocusedTextInputClient(NULL, NULL);
298   }
299 }
300 
GetFocusedTextInputClient() const301 TextInputClient* TSFBridgeDelegate::GetFocusedTextInputClient() const {
302   return client_;
303 }
304 
GetThreadManager()305 base::win::ScopedComPtr<ITfThreadMgr> TSFBridgeDelegate::GetThreadManager() {
306   DCHECK_EQ(base::MessageLoop::TYPE_UI, base::MessageLoop::current()->type());
307   DCHECK(IsInitialized());
308   return thread_manager_;
309 }
310 
CreateDocumentManager(TSFTextStore * text_store,ITfDocumentMgr ** document_manager,ITfContext ** context,DWORD * source_cookie)311 bool TSFBridgeDelegate::CreateDocumentManager(TSFTextStore* text_store,
312                                               ITfDocumentMgr** document_manager,
313                                               ITfContext** context,
314                                               DWORD* source_cookie) {
315   if (FAILED(thread_manager_->CreateDocumentMgr(document_manager))) {
316     DVLOG(1) << "Failed to create Document Manager.";
317     return false;
318   }
319 
320   DWORD edit_cookie = TF_INVALID_EDIT_COOKIE;
321   if (FAILED((*document_manager)->CreateContext(
322       client_id_,
323       0,
324       static_cast<ITextStoreACP*>(text_store),
325       context,
326       &edit_cookie))) {
327     DVLOG(1) << "Failed to create Context.";
328     return false;
329   }
330 
331   if (FAILED((*document_manager)->Push(*context))) {
332     DVLOG(1) << "Failed to push context.";
333     return false;
334   }
335 
336   if (!text_store || !source_cookie)
337     return true;
338 
339   base::win::ScopedComPtr<ITfSource> source;
340   if (FAILED(source.QueryFrom(*context))) {
341     DVLOG(1) << "Failed to get source.";
342     return false;
343   }
344 
345   if (FAILED(source->AdviseSink(IID_ITfTextEditSink,
346                                 static_cast<ITfTextEditSink*>(text_store),
347                                 source_cookie))) {
348     DVLOG(1) << "AdviseSink failed.";
349     return false;
350   }
351 
352   if (*source_cookie == TF_INVALID_COOKIE) {
353     DVLOG(1) << "The result of cookie is invalid.";
354     return false;
355   }
356   return true;
357 }
358 
InitializeDocumentMapInternal()359 bool TSFBridgeDelegate::InitializeDocumentMapInternal() {
360   const TextInputType kTextInputTypes[] = {
361     TEXT_INPUT_TYPE_NONE,
362     TEXT_INPUT_TYPE_TEXT,
363     TEXT_INPUT_TYPE_PASSWORD,
364     TEXT_INPUT_TYPE_SEARCH,
365     TEXT_INPUT_TYPE_EMAIL,
366     TEXT_INPUT_TYPE_NUMBER,
367     TEXT_INPUT_TYPE_TELEPHONE,
368     TEXT_INPUT_TYPE_URL,
369   };
370   for (size_t i = 0; i < arraysize(kTextInputTypes); ++i) {
371     const TextInputType input_type = kTextInputTypes[i];
372     base::win::ScopedComPtr<ITfContext> context;
373     base::win::ScopedComPtr<ITfDocumentMgr> document_manager;
374     DWORD cookie = TF_INVALID_COOKIE;
375     const bool use_null_text_store = (input_type == TEXT_INPUT_TYPE_NONE);
376     DWORD* cookie_ptr = use_null_text_store ? NULL : &cookie;
377     scoped_refptr<TSFTextStore> text_store =
378         use_null_text_store ? NULL : new TSFTextStore();
379     if (!CreateDocumentManager(text_store,
380                                document_manager.Receive(),
381                                context.Receive(),
382                                cookie_ptr))
383       return false;
384     const bool use_disabled_context =
385         (input_type == TEXT_INPUT_TYPE_PASSWORD ||
386          input_type == TEXT_INPUT_TYPE_NONE);
387     if (use_disabled_context && !InitializeDisabledContext(context))
388       return false;
389     tsf_document_map_[input_type].text_store = text_store;
390     tsf_document_map_[input_type].document_manager = document_manager;
391     tsf_document_map_[input_type].cookie = cookie;
392   }
393   return true;
394 }
395 
InitializeDisabledContext(ITfContext * context)396 bool TSFBridgeDelegate::InitializeDisabledContext(ITfContext* context) {
397   base::win::ScopedComPtr<ITfCompartmentMgr> compartment_mgr;
398   if (FAILED(compartment_mgr.QueryFrom(context))) {
399     DVLOG(1) << "Failed to get CompartmentMgr.";
400     return false;
401   }
402 
403   base::win::ScopedComPtr<ITfCompartment> disabled_compartment;
404   if (FAILED(compartment_mgr->GetCompartment(
405       GUID_COMPARTMENT_KEYBOARD_DISABLED,
406       disabled_compartment.Receive()))) {
407     DVLOG(1) << "Failed to get keyboard disabled compartment.";
408     return false;
409   }
410 
411   base::win::ScopedVariant variant;
412   variant.Set(1);
413   if (FAILED(disabled_compartment->SetValue(client_id_, &variant))) {
414     DVLOG(1) << "Failed to disable the DocumentMgr.";
415     return false;
416   }
417 
418   base::win::ScopedComPtr<ITfCompartment> empty_context;
419   if (FAILED(compartment_mgr->GetCompartment(GUID_COMPARTMENT_EMPTYCONTEXT,
420                                              empty_context.Receive()))) {
421     DVLOG(1) << "Failed to get empty context compartment.";
422     return false;
423   }
424   base::win::ScopedVariant empty_context_variant;
425   empty_context_variant.Set(static_cast<int32>(1));
426   if (FAILED(empty_context->SetValue(client_id_, &empty_context_variant))) {
427     DVLOG(1) << "Failed to set empty context.";
428     return false;
429   }
430 
431   return true;
432 }
433 
IsFocused(ITfDocumentMgr * document_manager)434 bool TSFBridgeDelegate::IsFocused(ITfDocumentMgr* document_manager) {
435   base::win::ScopedComPtr<ITfDocumentMgr> focused_document_manager;
436   if (FAILED(thread_manager_->GetFocus(focused_document_manager.Receive())))
437     return false;
438   return focused_document_manager.IsSameObject(document_manager);
439 }
440 
IsInitialized()441 bool TSFBridgeDelegate::IsInitialized() {
442   return client_id_ != TF_CLIENTID_NULL;
443 }
444 
UpdateAssociateFocus()445 void TSFBridgeDelegate::UpdateAssociateFocus() {
446   if (attached_window_handle_ == NULL)
447     return;
448   TSFDocument* document = GetAssociatedDocument();
449   if (document == NULL) {
450     ClearAssociateFocus();
451     return;
452   }
453   // NOTE: ITfThreadMgr::AssociateFocus does not increment the ref count of
454   // the document manager to be attached. It is our responsibility to make sure
455   // the attached document manager will not be destroyed while it is attached.
456   // This should be true as long as TSFBridge::Shutdown() is called late phase
457   // of UI thread shutdown.
458   base::win::ScopedComPtr<ITfDocumentMgr> previous_focus;
459   thread_manager_->AssociateFocus(
460       attached_window_handle_, document->document_manager.get(),
461       previous_focus.Receive());
462 }
463 
ClearAssociateFocus()464 void TSFBridgeDelegate::ClearAssociateFocus() {
465   if (attached_window_handle_ == NULL)
466     return;
467   base::win::ScopedComPtr<ITfDocumentMgr> previous_focus;
468   thread_manager_->AssociateFocus(
469       attached_window_handle_, NULL, previous_focus.Receive());
470 }
471 
GetAssociatedDocument()472 TSFBridgeDelegate::TSFDocument* TSFBridgeDelegate::GetAssociatedDocument() {
473   if (!client_)
474     return NULL;
475   TSFDocumentMap::iterator it =
476       tsf_document_map_.find(client_->GetTextInputType());
477   if (it == tsf_document_map_.end()) {
478     it = tsf_document_map_.find(TEXT_INPUT_TYPE_TEXT);
479     // This check is necessary because it's possible that we failed to
480     // initialize |tsf_document_map_| and it has no TEXT_INPUT_TYPE_TEXT.
481     if (it == tsf_document_map_.end())
482       return NULL;
483   }
484   return &it->second;
485 }
486 
487 }  // namespace
488 
489 
490 // TsfBridge  -----------------------------------------------------------------
491 
TSFBridge()492 TSFBridge::TSFBridge() {
493 }
494 
~TSFBridge()495 TSFBridge::~TSFBridge() {
496 }
497 
498 // static
Initialize()499 bool TSFBridge::Initialize() {
500   if (base::MessageLoop::current()->type() != base::MessageLoop::TYPE_UI) {
501     DVLOG(1) << "Do not use TSFBridge without UI thread.";
502     return false;
503   }
504   if (!tls_tsf_bridge.initialized()) {
505     tls_tsf_bridge.Initialize(TSFBridge::Finalize);
506   }
507   TSFBridgeDelegate* delegate =
508       static_cast<TSFBridgeDelegate*>(tls_tsf_bridge.Get());
509   if (delegate)
510     return true;
511   delegate = new TSFBridgeDelegate();
512   tls_tsf_bridge.Set(delegate);
513   return delegate->Initialize();
514 }
515 
516 // static
ReplaceForTesting(TSFBridge * bridge)517 TSFBridge* TSFBridge::ReplaceForTesting(TSFBridge* bridge) {
518   if (base::MessageLoop::current()->type() != base::MessageLoop::TYPE_UI) {
519     DVLOG(1) << "Do not use TSFBridge without UI thread.";
520     return NULL;
521   }
522   TSFBridge* old_bridge = TSFBridge::GetInstance();
523   tls_tsf_bridge.Set(bridge);
524   return old_bridge;
525 }
526 
527 // static
Shutdown()528 void TSFBridge::Shutdown() {
529   if (base::MessageLoop::current()->type() != base::MessageLoop::TYPE_UI) {
530     DVLOG(1) << "Do not use TSFBridge without UI thread.";
531   }
532   if (tls_tsf_bridge.initialized()) {
533     TSFBridgeDelegate* delegate =
534         static_cast<TSFBridgeDelegate*>(tls_tsf_bridge.Get());
535     tls_tsf_bridge.Set(NULL);
536     delete delegate;
537   }
538 }
539 
540 // static
GetInstance()541 TSFBridge* TSFBridge::GetInstance() {
542   if (base::MessageLoop::current()->type() != base::MessageLoop::TYPE_UI) {
543     DVLOG(1) << "Do not use TSFBridge without UI thread.";
544     return NULL;
545   }
546   TSFBridgeDelegate* delegate =
547       static_cast<TSFBridgeDelegate*>(tls_tsf_bridge.Get());
548   DCHECK(delegate) << "Do no call GetInstance before TSFBridge::Initialize.";
549   return delegate;
550 }
551 
552 // static
Finalize(void * data)553 void TSFBridge::Finalize(void* data) {
554   TSFBridgeDelegate* delegate = static_cast<TSFBridgeDelegate*>(data);
555   delete delegate;
556 }
557 
558 }  // namespace ui
559