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