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 |