| OLD | NEW |
| (Empty) |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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(base::MessageLoopForUI::IsCurrent()); | |
| 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::MessageLoopForUI::IsCurrent()) { | |
| 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::MessageLoopForUI::IsCurrent()) { | |
| 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::MessageLoopForUI::IsCurrent()) { | |
| 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::MessageLoopForUI::IsCurrent()) { | |
| 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 | |
| OLD | NEW |