Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(241)

Side by Side Diff: ui/base/ime/win/tsf_bridge.cc

Issue 149073009: Remove InputMethodTSF, TSFEventRouter, TSFTextStore, TSFBridge (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: revert input_method_imm32 changes Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « ui/base/ime/win/tsf_bridge.h ('k') | ui/base/ime/win/tsf_event_router.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « ui/base/ime/win/tsf_bridge.h ('k') | ui/base/ime/win/tsf_event_router.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698