Home | History | Annotate | Download | only in win
      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:
     98     TSFDocument() : cookie(TF_INVALID_COOKIE) {}
     99     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 
    137 TSFBridgeDelegate::TSFBridgeDelegate()
    138     : client_id_(TF_CLIENTID_NULL),
    139       client_(NULL),
    140       attached_window_handle_(NULL) {
    141 }
    142 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    301 TextInputClient* TSFBridgeDelegate::GetFocusedTextInputClient() const {
    302   return client_;
    303 }
    304 
    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 
    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 
    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 
    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 
    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 
    441 bool TSFBridgeDelegate::IsInitialized() {
    442   return client_id_ != TF_CLIENTID_NULL;
    443 }
    444 
    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 
    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 
    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 
    492 TSFBridge::TSFBridge() {
    493 }
    494 
    495 TSFBridge::~TSFBridge() {
    496 }
    497 
    498 // static
    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
    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
    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
    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
    553 void TSFBridge::Finalize(void* data) {
    554   TSFBridgeDelegate* delegate = static_cast<TSFBridgeDelegate*>(data);
    555   delete delegate;
    556 }
    557 
    558 }  // namespace ui
    559